一道状压好题(少有的没看题解一遍$AC$的不是水题(自认为)的题(看来是我太菜了))
首先看到$k\leqslant 16$,$emm$,要么暴搜,要么状压
再看$n\leqslant 10^5$,$emm$,状压实锤了,但这$n$太大,显然其中状压$dp$的状态和转移中都不能有$n$(一有就炸了)
看范围先想一维,令$f[i]$表示状态$i$下的什么
有P4329 [COCI2006-2007#1] Bond的经验,我们知道状态并不只表示了其中的选或不选,还可以表示选了多少个和选了哪些
在这道题下,选了哪些就尤为重要了(因为就可以统计答案了),那差的就是能不能买到$n$个或者说能买多少个
试着令$f[i]$表示状态$i$下最多能买多少个,枚举当前用哪个硬币(假设是第$j$号硬币)
那之前的状态为$f[i-\left \{ j \right \}]$(也就是说$j$号硬币要从$f[i-\left \{ j \right \}]+1$号物品开始买),
那现在的问题就只剩下$j$号硬币从某处开始最多能买多少个物品,这可以用前缀和$+$二分提前预处理出来
令$cnt[i][j]$表示$i$号硬币从$j$处开始最多买多少个,$r$表示最远的那个编号
显然有$$sum[r]-sum[j-1]\leqslant val[i]$$
即$$r=upperbound(val[i]+sum[j-1])-1$$
如此,转移就出来了$$f[i] = max\left \{ f[i-\left \{ j \right \}]+cnt[j][f[i-\left \{ j \right \}]+1] \right \}$$
上代码(写得好累啊$QwQ$)
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #define ll long long 6 using namespace std; 7 8 template <typename T> void in(T &x) { 9 x = 0; T f = 1; char ch = getchar(); 10 while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} 11 while( isdigit(ch)) {x = 10 * x + ch - 48; ch = getchar();} 12 x *= f; 13 } 14 15 template <typename T> void out(T x) { 16 if(x < 0) x = -x , putchar('-'); 17 if(x > 9) out(x/10); 18 putchar(x%10 + 48); 19 } 20 //------------------------------------------------------- 21 22 const int N = 1e6+5,M = 17; 23 24 int k,n; 25 int val[M],cst[M],cnt[M][N]; 26 ll sum[N]; 27 int f[1<<M]; 28 ll ans = -1; 29 30 int main() { 31 int i,j; 32 in(k); in(n); 33 for(i = 1;i <= k; ++i) in(val[i]); 34 for(i = 1;i <= n; ++i) in(cst[i]),sum[i] = sum[i-1]+cst[i]; 35 for(i = 1;i <= k; ++i) { 36 for(j = 1;j <= n; ++j) { 37 cnt[i][j] = upper_bound(sum+1,sum+n+1,val[i]+sum[j-1])-sum-1-j+1;//r-j+1//debug val -> cst 38 } 39 }//预处理出i硬币在位置j向后延伸的最长长度 40 memset(f,~0x3f,sizeof(f)); 41 f[0] = 0; 42 for(i = 1;i < (1<<k); ++i) { 43 for(j = 1;j <= k; ++j) { 44 if(i&(1<<(j-1))) f[i] = max(f[i],f[i^(1<<(j-1))]+cnt[j][f[i^(1<<(j-1))]+1]); 45 } 46 if(f[i] >= n) { 47 ll res = 0; 48 for(j = 1;j <= k; ++j) if(!(i&(1<<(j-1)))) res += val[j]; 49 ans = max(res,ans); 50 } 51 } 52 out(ans); 53 return 0; 54 }