HDU 4406 GPA
题目大意:给出计算GPA的公式。现在又N天时间,每天有k节课,每节课上,你只能复习一门课程,复习之后,该门课程的成绩可以提高1分。当然你也可以花好几节课的时间在一门课程上来使得这门课的成绩提高的更多,但是课程的得分不能超过100。然而,不是每节课上你想复习什么课程就可以什么课程,每天你只能复习一些课程。现在要你去把GPA刷高,前提是不能有挂科(<60分),要你计算可以刷到的最高的GPA。
输入:N,K,M分别表示N天,每天K节课,一共有M门课,接下来的一行M个数表示每门课的学分,下一行M个数表示每门课在不复习的情况下可以得到的分数,再接下来的N行有一个N*M的矩阵,第i行的第j个数若是1表示第i天你可以复习第j门课,若是0就不能。
输出:GPA,保留6位有效数字,如果还是有挂科的就输出0.000000
首先这应该是一个费用流问题,求的是最大费用流。
这个费用的问题怎么解决,首先看公式p=f(x)=4.0 - 3.0*(100-x)*(100-x)/1600,这是一个凸函数,那么就必须用不同的边表示不同的情况。就好比HDU3667那个题。
接下来的问题是如何保证在没有挂科的前提下得到的最大费用了。
对每一门不及格的课程i,假设它的基础分是score[i],那么加一条(S,i,60-score[i],inf)的边,这样就可以保证在求最大费用的时候,肯定最先去考虑这些费用最大的边了。处理完这些不及格的课程以后,现在,所有的课程就都是及格的了。我们知道x是离散的,那么每提高一分,就可以获得一个(f(x+1)-f(x))*credit[i]的价值,所以,这里的每一分都对应一条边(S,i,1, (f(x+1)-f(x))*credit[i] )。另外,如果第i天可以修第j门课程,对应一条(j,i,inf,0)的边,对于每一天i,对应一条边(i,T,K,0)的边,这样跑一遍费用流就可以把每门课最后可以得多少分处理出来了。具体方法是找到 把 从S出发连向课程i的所有正向边的流量 加上原本第i门课程都得分数 就能得到第i门课的实际得分(当然这应该只是使得GPA最大的方案之一),然后直接根据公式计算即可。这里参考了这个博客:http://blog.csdn.net/lj_acm/article/details/11578975,使用了一种更加方便的方法,直接利用费用来计算最后的GPA,详情见代码:
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 #include <queue> 5 #define maxn 110 6 #define maxm 3010 7 #define INF 100000 8 using namespace std; 9 struct MCMF{ 10 int src,sink,e,n; 11 int first[maxn]; 12 int cap[maxm],v[maxm],next[maxm]; 13 double cost[maxm]; 14 void init(){ 15 e = 0; 16 memset(first,-1,sizeof(first)); 17 } 18 19 void add_edge(int a,int b,int cc,double ww){ 20 //printf("add:%d to %d,cap = %d,cost = %.3f\n",a,b,cc,ww); 21 cap[e] = cc;cost[e] = ww;v[e] = b; 22 next[e] = first[a];first[a] = e++; 23 cap[e] = 0;cost[e] = -ww;v[e] = a; 24 next[e] = first[b];first[b] = e++; 25 } 26 27 double d[maxn]; 28 int pre[maxn],pos[maxn]; 29 bool vis[maxn]; 30 31 bool spfa(int s,int t){ 32 memset(pre,-1,sizeof(pre)); 33 memset(vis,0,sizeof(vis)); 34 queue<int> Q; 35 for(int i = 0;i <= n;i++) d[i] = 1.0*INF; 36 Q.push(s);pre[s] = s;d[s] = 0;vis[s] = 1; 37 while(!Q.empty()){ 38 int u = Q.front();Q.pop(); 39 vis[u] = 0; 40 for(int i = first[u];i != -1;i = next[i]){ 41 if(cap[i] > 0 && d[u] + cost[i] < d[v[i]]){ 42 d[v[i]] = d[u] + cost[i]; 43 pre[v[i]] = u;pos[v[i]] = i; 44 if(!vis[v[i]]) vis[v[i]] = 1,Q.push(v[i]); 45 } 46 } 47 } 48 return pre[t] != -1; 49 } 50 51 double Mincost; 52 int Maxflow; 53 54 double MinCostFlow(int s,int t,int nn){ 55 Mincost = 0,Maxflow = 0,n = nn; 56 while(spfa(s,t)){ 57 int min_f = INF; 58 for(int i = t;i != s;i = pre[i]) 59 if(cap[pos[i]] < min_f) min_f = cap[pos[i]]; 60 Mincost += d[t] * min_f; 61 Maxflow += min_f; 62 for(int i = t;i != s;i = pre[i]){ 63 cap[pos[i]] -= min_f; 64 cap[pos[i]^1] += min_f; 65 } 66 } 67 return Mincost; 68 } 69 }; 70 MCMF g; 71 72 int N,M,K; 73 double f[maxn],credit[maxn]; 74 int score[maxn]; 75 76 int main(){ 77 for(int i = 60;i <= 100;i++) 78 f[i] = 4.0 - 3.0*(100-i)*(100-i)/1600; 79 while(scanf("%d%d%d",&N,&K,&M),N+K+M){ 80 g.init(); 81 int S = 0,T = N+M+1; 82 double tot_credit = 0; 83 double sum = 0,basic = 0; 84 for(int i = 1;i <= M;i++) 85 scanf("%lf",&credit[i]),tot_credit += credit[i]; 86 for(int i = 1;i <= M;i++){ 87 scanf("%d",&score[i]); 88 if(score[i] < 60){ 89 g.add_edge(S,i,60-score[i],-1.0*INF); 90 sum += (60-score[i])*INF; 91 score[i] = 60; 92 } 93 basic += f[score[i]] * credit[i]; 94 for(int j = score[i]+1;j <= 100;j++) 95 g.add_edge(0,i,1,-(f[j]-f[j-1])*credit[i]); 96 } 97 for(int i = 1;i <= N;i++) 98 for(int j = 1;j <= M;j++){ 99 int tmp; 100 scanf("%d",&tmp); 101 if(tmp){ 102 g.add_edge(j,i+M,K,0); 103 } 104 } 105 for(int i = 1;i <= N;i++) 106 g.add_edge(i+M,T,K,0); 107 double ans = -g.MinCostFlow(S,T,T); 108 if(ans >= sum){ 109 printf("%.6f\n",(basic+ans-sum)/tot_credit); 110 } 111 else printf("0.000000\n"); 112 } 113 return 0; 114 }