uva12589
题目大意:给n(n<=50)个向量(xi,yi) (0<=xi<=yi<=50),选出其中k(1<=k<=n)个,从(0,0)点开始,依次首尾相连,求此k个向量与x正半轴围成的最大面积的两倍并输出。
初步想法,向量都在第一象限,所以最优解一定是选中k个排成上凸曲线。故第一步是按照向量斜率排序!
然后就是迭代dp了。
先看看暴力方程dp[i][j][y]=max(dp[i][j][y],dp[o][j-1][y-li[i].y]+f(li[i].x,li[i].y,y)) (对于任意的i>o>=j)这个时候需要枚举o,效率是很不乐观的。
优化:假设dp[i][*][*]都已经求出来了。
那么dp[i+1][j][y]=max(dp[i][j][y],dp[i][j-1][y-li[i+1].y]+f(li[i+1].x,li[i+1].y,y)),这样i推出i+1就是O(1)的效率了,初始状态就是dp[x][0][0]=0。
再加上一点剪枝,这样就是很好的solution了。
再看看样例,有T<=110个case,所以不能每次循环都memset(dp,0,sizeof(dp))一遍,加访问标记。(建议ACMer新手每次一定要记得看看样例有多少组)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <cmath> 6 using namespace std; 7 int dp[51][51][2501]; 8 int stamps[51][51][2501]; 9 struct line{ 10 int x,y; 11 friend bool operator<(line S,line T){ 12 return S.y*T.x > T.y*S.x; 13 } 14 }li[55]; 15 int main() 16 { 17 int cases; cin>>cases; 18 int n,k; 19 for(int cas=1;cas<=cases;cas++){ 20 scanf("%d%d",&n,&k); 21 for(int i=1;i<=n;i++) 22 scanf("%d%d",&li[i].x,&li[i].y); 23 for(int i=0;i<=n;i++) 24 dp[i][0][0]=0,stamps[i][0][0]=cas; 25 sort(li+1,li+n+1); 26 int t; 27 for(int i=1;i<=n;i++){ 28 int maxj=min(i,k),minj=max(1,k-(n-i+1)); 29 for(int j=minj;j<=maxj;j++){ 30 for(int y=2500;y>=0;y--){ 31 dp[i][j][y]=0; 32 if(i>j && stamps[i-1][j][y]>=cas) dp[i][j][y]=dp[i-1][j][y], stamps[i][j][y]=cas; 33 if((y-li[i].y)>=0 && stamps[i-1][j-1][y-li[i].y]>=cas) 34 dp[i][j][y] = max(dp[i][j][y],dp[i-1][j-1][y-li[i].y]+(y+y-li[i].y)*li[i].x), stamps[i][j][y]=cas; 35 } 36 } 37 } 38 int ans=0; 39 for(int y=2500;y>=0;y--) 40 if(stamps[n][k][y]>=cas) ans=max(ans,dp[n][k][y]); 41 printf("Case %d: %d\n",cas,ans); 42 } 43 return 0; 44 }