Is there a dynamic programming solution to the student allocation problem?
This sounds like it could be solved using a flow network.

Define the graph $G=(V,E)$ such that:


Add a starting node $s$. It will be the "source" node in the flow-graph.
Add a node $v_{s_i}$ for every student $s_i$.
Add a node $v_{p_j}$ for every project $p_j$.
Add the final node $t$. It will be the "sink" node for the flow-graph.


Now, start adding the edges and their weights as following:


For every $s_i\in S$, add an edge $(s,v_{s_i})$ of weight 1.
For every $s_i\in S$ and every preferred project of his, $p_j$, add an edge $(v_{s_i},v_{p_j})$ with weight 1.
Finally, for every project $p_j$ add an edge $(v_{p_j},t)$ of weight $c$.


Run the Ford Fulkerson algorithm on this graph $G$ we defined, and it will spit out an integer flow $f$. Assign a student $s_i$ to project $p_j$ if there is flow between $v_{s_i}$ and $v_{p_j}$ in $f$. Notice there might be students that wont be assigned anything in that way. They cannot be assigned to anything they prefer - so just assign them in any free project.

This algorithm runs in time $\mathcal O(|S||P|)$ since $|V|=\mathcal O(|S|+|P|)=\mathcal O(|P|)$, and each student has up to 3 preferred project so $|E|=\mathcal O(|V|)=\mathcal O(|P|)$ as well and the value of $f$, $|f|\le|S|=O(|S|)$.

It also provides the best possible solution, since it always maximizes the flow's value which is the number you want to maximize (think why).