HDU 4182 Judges' response(01背包+TSP状态压缩DP)
http://acm.hdu.edu.cn/showproblem.php?pid=4281
题意:本题有两问:首先是有n-1个物品,每个物品一个重量w,然后每个人有一个重量上限M,问你最小需要派几个人才能收集完所有物品.
第二问是:人和所有物品都有一个初始坐标,且人数无限制,在第一问的基础上,不超过人的负重的情况下,要拿走所有物品,人需要走多少路(人还要回到原点)?
分析:
首先第一问:
我们先求出所有物品的组合状态st,如果st的总重<=M,那么它就是一个合法状态.然后用dp1[i]表示当收集了状态i的物品时,需要的最少人数.
这里的dp1[i]采用了滚动数组,其实应该是dp1[i][j]表示决策完前i个物品时,当前状态为j时需要的最少人数.
dp1[i][j+st]= min(dp1[i-1][j+st] , dp1[i-1][j]+1) st与j的交集为空(可以不为空,代码实现就不没考虑空),其实就算不为空也行..
且st=state[i],即st是第i个有效状态
初值为dp1[0][0]=0,其他dp1[0][j]=INF
dp1[i]=min(dp1[i],dp1[i-j]+1); j是i的子集的合法状态
初始值:dp1[0]=0,其他dp1[i]=INF.
第二问:
我们首先要求的是cost[i][j]表示当前在i点,走过的节点状态为j时需要走的总距离.其中这个j状态是第一问中计算的合法状态,并不是所有状态都算的.
cost[i][j+{k}] = min( cost[k][ j] +dist[i][k] ) 其中k不属于j.
然后利用cost[i][j]可以求出np[j],np[j]就是从0点出发走过了j集合的点又回到0点的最小距离,其中j集合必定包含0,但是在程序代码中这点没体现出来.
其中np[j]= min( cost[i][j] ) 其中i属于集合j中.
最后我们合并np的结果.由于上一步计算的np[j]中的状态j都是包括了前置1的,即j&1!=0的(想想为什么),所以后面把j分成两个子集的话,如果子集不带前置1,就直接可能为INF值,导致结果错误.
AC代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<cstring> #include<cmath> using namespace std; #define INF (1<<29) int n,m; int x[20],y[20],c[20]; int state[1<<17],cnt;//cnt表示合法状态有多少个,state[i]=x,表示第i个合法状态是x bool isok[1<<17]; int dp1[1<<17],cost[17][1<<17]; int dist[20][20]; int np[1<<17]; bool good(int st) { int sum=0; for(int i=0;i<n;i++)if(st&(1<<i)) sum +=c[i]; return sum<=m; } int solve_1() { cnt=0; for(int i=0;i<(1<<n);i++) { isok[i]=good(i); if(isok[i]) state[cnt++]=i; } for(int i=0;i<(1<<n);i++) dp1[i]=INF; dp1[0]=0; for(int i=0;i<cnt;i++) { for(int j=(1<<n)-1;j>=0;j--)if(dp1[j]!=INF) dp1[j|state[i]]=min(dp1[j|state[i]],dp1[j]+1); } return dp1[(1<<n)-1]; } int solve_2() { //计算任意两点间的距离 memset(dist,0,sizeof(dist)); for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) dist[i][j]=dist[j][i]= ceil(sqrt((double)(x[i] - x[j])*(x[i] - x[j]) + (y[i] - y[j])*(y[i] - y[j]))); for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++) cost[i][j]=INF;//cost[i][j]表示当前在i点,已经走过了集合j中的点,所需要的最短距离 cost[0][1]=0; for(int i=0;i<(1<<n);i++) for(int j=0;j<n;j++)if( (i&(1<<j)) )//j在集合i中 for(int k=0;k<n;k++)if( (i&(1<<k))==0 )//k不在集合i中 cost[k][i|(1<<k)] = min(cost[k][i|(1<<k)] , cost[j][i]+dist[j][k]); for(int i=0;i<(1<<n);i++) { np[i]=INF; if(isok[i]) for(int j=0;j<n;j++)if(i&(1<<j))//j在集合i中 { np[i] = min(np[i],cost[j][i]+dist[j][0]);//这里计算出来的有效np[i]的i&1都是!=0的 //因为cost中的i&1 !=0 } } for(int i=1;i<(1<<n);i++) if(i&1)for(int j=(i-1)&i;j;j=(j-1)&i) np[i]=min(np[i],np[j|1]+np[(i-j)|1]);//如果此处不|1的话,那么有可能分成的集合j和(i-j)因为没有前置1从而是INF的值 return np[(1<<n)-1]; } int main() { while(scanf("%d%d",&n,&m)==2) { for(int i=0;i<n;i++) scanf("%d%d",&x[i],&y[i]); for(int i=0;i<n;i++) scanf("%d",&c[i]); int ans1=solve_1(); int ans2=solve_2(); if(ans1==INF) printf("-1 -1\n"); else printf("%d %d\n",ans1,ans2); } return 0; }