P4377 [USACO18OPEN]Talent Show G
考察:01分数规划+01背包
错误思路:
二分求最大值,check函数里对 t[i] - mid*w[i]排序,从大到小选,只要和为sum就一直选,最后检测选择的W是否>=m
这个思路错在不能从大到小选,因为存在t[i] - mid*w[i]很小,但是w[i]很大使原本从大到小的W>=m
思路:
01分数规划二分是没错的,主要在于check函数.这里的牛是随意的排列组合,组合问题比起dfs,都可以考虑一下背包dp.
这个01背包实际是前i头牛,重量至少是j的最大值.因为f[i][j]要尽可能的大,所以f[i][m]可以由所有能从f[i-1][j+w[i]] (j+w[i]>=m) 的状态推来.
实际dp转移方程是
f[i][m] = max(f[i-1][m],f[i-1][j+w[i])+t[i] - mid*w[i] j+w[i]>=m 选第i个
f[i][j+w[i]] = max(f[i-1][j]+c[i],f[i-1][j+w[i]) j+w[i]<m 选第i个
上面都是选第i个的dp转移
但是注意,这样只有j+w[i]和m状态转移了,还有f[i][j] = max(f[i-1][j],f[i][j]) 需要转移.
但是如果我们压缩为一维,后面j转移就不需要.
1 #include <iostream> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 typedef long long LL; 6 const double eps = 1e-6; 7 const int N = 260,M = 1010,INF = 0x3f3f3f3f; 8 int n,m,w[N],t[N]; 9 double f[M],c[N]; 10 bool check(double mid) 11 { 12 for(int j=1;j<=m;j++) f[j] = -INF; 13 for(int i=1;i<=n;i++) c[i] = t[i] - mid*w[i]; 14 for(int i=1;i<=n;i++) 15 for(int j=m;j>=0;j--) 16 if(j+w[i]>=m) 17 f[m] = max(f[m],f[j]+c[i]); 18 else 19 f[j+w[i]] = max(f[j]+c[i],f[j+w[i]]); 20 if(f[m]>=0) return 1; 21 return 0; 22 } 23 int main() 24 { 25 scanf("%d%d",&n,&m); 26 for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&t[i]); 27 double l = 0,r = 1001; 28 while(r-l>=eps) 29 { 30 double mid = (l+r)/2; 31 if(check(mid)) l = mid; 32 else r = mid; 33 } 34 int ans = r*1000; 35 printf("%d\n",ans); 36 return 0; 37 }