HDU 4341 Gold miner(分组背包)
http://acm.hdu.edu.cn/showproblem.php?pid=4341
题意:
一个人在原点(0,0)抓金子,每块金子是二维坐标平面的一个点(x,y)且y>0. 每块金子有一个价值v和获得需要的时间t。如果多个金子在一条从原点射出的直线上,那只能先抓近的,再抓远的。求在给定时间T下,所能获得的最大价值。
分析:
首先想想如果所有点都不共线是什么情况? 就是在给定时间T内要获取最大的价值和的点, 且所有点都可以任意选取. 那么这就是一个01背包问题. 不过如果存在共线的点,那么如何处理呢?
我们把所有共(从原点射出的)直线的点分成1组, 然后每个组内只能选1个点. 比如如果你选了该组第i远的点, 那么你就必须把第1个第2个..第i-1 和第i个点都选了. 所以每组i点的花费和价值都是前面所有点的累加和.
现在的问题就变成了分组背包问题了: 对于每个给定的组, 组内最多只能选1个物品, 问你能获得的最大价值和.
我们令dp[i][j]==x表示只选前i组的物品,总花费<=j时能获得的最大价值==x.
初始化: dp全为0.
状态转移: dp[i][j] = max( dp[i-1][j] , dp[i-1][j-cost[k]]+val[k]) 其中cost[k]和val[k]是第i组物品中的第k个物品的花费和价值.
最终所求: dp[n][T].
注意程序实现的3层循环不能互换顺序, 因为要保证滚动数组的情况下, 实现上述的状态转移方程.
AC代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> using namespace std; const int maxn=200+5; struct Point { int x,y; int cost,val; bool operator<(const Point &rhs)const//极角排序 { return x*rhs.y-y*rhs.x<0 || (x*rhs.y-y*rhs.x==0 && y<rhs.y); } bool operator==(const Point &rhs)const//共线判断 { return x*rhs.y-y*rhs.x==0; } }p[maxn]; int N,T; int n; //多少组 int num[maxn];//num[i]表示第i组的物品数 int cost[maxn][maxn];//cost[i][j]==x表示第i组第j个物品的花费 int val[maxn][maxn]; //cost[i][j]==x表示第i组第j个物品的价值 int dp[40000+5]; int main() { int n,kase=0; while(scanf("%d%d",&N,&T)==2) { //读取输入 for(int i=0;i<N;i++) scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i].cost,&p[i].val); sort(p,p+N); //原始物品分成n组,每组只取1个 n=1; num[n]=1; cost[n][num[n]]=p[0].cost; val[n][num[n]]=p[0].val; for(int i=1;i<N;i++) { if(p[i]==p[i-1])//共线 { num[n]++; cost[n][num[n]]=cost[n][num[n]-1]+p[i].cost; val[n][num[n]]=val[n][num[n]-1]+p[i].val; } else//不共线 { n++; num[n]=1; cost[n][num[n]]=p[i].cost; val[n][num[n]]=p[i].val; } } //分组背包过程 memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) for(int j=T;j>=0;j--) for(int k=1;k<=num[i];k++) if(j>=cost[i][k]) dp[j] = max(dp[j], dp[j-cost[i][k]]+val[i][k]); printf("Case %d: %d\n",++kase,dp[T]); } return 0; }