YZOJ P2202 Legend VII – Ornament
时间限制:1000MS 内存限制:131072KB
难度:5.0

第一行有两个整数 N 和 Q,表示商店有 N 个装饰品,一共有 Q 个询问。
第二行有 N 对整数,每 i 对整数 p_i, b_i 表示第 i 个装饰品的价格和好看度。
接下来 Q 行,每行两个整数 a, c,分别描述 Q 个询问。
对于每个询问输出一行,一个整数表示最大好看度。
|
3 5 2 3 1 3 1 2 1 2 1 1 3 1 3 2 3 3 |
对于 30\% 的数据,N \leq 100, Q \leq 1000 。
对于 100\% 的数据,N \leq 1000, Q \leq 100000, 1 \leq a \leq N, c \leq 1000 。
按照惯例,这题可以用一种奇怪的做法卡过去:正反两遍背包,然后对于每个询问 O(c) 查找答案。时间复杂度 O(Qc \approx 10^8) ,数据比较水就卡过去了。
详见代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
#include <cstdio> #include <cstdlib> #include <cstring> #include <climits> #define _max(_a,_b) ((_a)>(_b)?(_a):(_b)) inline int getnum() { register char c=getchar(); while(!(c>='0' && c<='9')) c=getchar(); register int a=0; while(c>='0' && c<='9') { a*=10;a+=c-'0'; c=getchar(); } return a; } int p[1050],b[1050]; int f[1050][1050],rf[1050][1050]; int main() { register int N=getnum(),Q=getnum(); for(register int i=1;i<=N;i++) p[i]=getnum(),b[i]=getnum(); for(register int i=1;i<=N;i++) { for(register int j=0;j<p[i];j++) f[i][j]=f[i-1][j]; for(register int j=p[i];j<=1000;j++) f[i][j]=_max(f[i-1][j],f[i-1][j-p[i]]+b[i]); } for(register int i=N;i>=1;i--) { for(register int j=0;j<p[i];j++) rf[i][j]=rf[i+1][j]; for(register int j=p[i];j<=1000;j++) rf[i][j]=_max(rf[i+1][j],rf[i+1][j-p[i]]+b[i]); } for(register int q=1;q<=Q;q++) { register int a=getnum(),c=getnum(); register int ans=0; // 去掉第 a 个物品,重新枚举容量 for(register int j=0;j<=c;j++) ans=_max(ans,f[a-1][j]+rf[a+1][c-j]); printf("%d\n",ans); } return 0; } |
但是我们必须思考正解而不是套算法!
首先,由于“删除”这个操作不是很好实现,所以把思维逆转过来,考虑“增加”这个操作。
接着,还发现一个性质:如果不选的物品 a 在区间 [l, r] 中,那么 a 不是在 [l, \lfloor\frac{l+r}{2}\rfloor] 中就是在 [\lfloor\frac{l+r}{2}\rfloor+1, r] 中(废话)。这提示我们,可以用分治的思想把这个问题分成多个子问题来解决。
对于一个区间 [l, r] ,如果 a 在 [l, \lfloor\frac{l+r}{2}\rfloor] 中,那么另一个区间 [\lfloor\frac{l+r}{2}\rfloor+1, r] 中的物品都是可以选的,所以对这些物品做01背包,然后继续处理 [l, \lfloor\frac{l+r}{2}\rfloor] ;若 a 在 [\lfloor\frac{l+r}{2}\rfloor+1, r] 中也是同样。
但是很明显,在分治的时候需要不选 [l, r] 区间物品,其他物品都选时的动态规划 f 数组作为初始状态,这样才能继续对这个区间进行规划和分割。同时必须在回溯的时候把这个数组还原。对于每次递归和回溯都重新对除了 [l, r] 中的物品做一遍01背包代价是很大的,可以达到 O(N^2c) 等于是退化了。
注意到分治的递归树最多只有 log_{2}{N^2} \approx 19 层,所以可以直接把每一层的动态规划 f 数组都存下来,在对这一层进行规划的时候,直接把上一层的 f 当做初始状态即可。这样回溯的时候也不必考虑如何保存并还原 f 数组了。
最后,当分治到 l=r 时,这个 a 元素也就被确定下来了,更新去掉 a 的答案的 f’ 数组即可。
这样,时间复杂度就只有 O(NclogN) 足以通过本题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
#include <cstdio> #include <cstdlib> #include <cstring> #include <climits> #define _max(_a,_b) ((_a)>(_b)?(_a):(_b)) #define MAXBFN 524288 char buffer[MAXBFN+1],*S,*T; inline char GetChar() { if(S==T) { T=(S=buffer)+fread(buffer,1,MAXBFN,stdin); if(S==T)return EOF; } return *S++; } char pbuf[MAXBFN+1],*pp=pbuf; inline void PutChar(const char&c) { if(pp-pbuf==MAXBFN) fwrite(pbuf,1,MAXBFN,stdout),pp=pbuf; *pp++=c; } inline void PrintNum(long long a) { if(a<0) { PutChar('-'); PrintNum(-a); return; } if(a>9) PrintNum(a/10); PutChar(a%10+'0'); } inline int getnum() { register char c=GetChar(); while(!(c>='0' && c<='9')) c=GetChar(); register int a=0; while(c>='0' && c<='9') { a*=10;a+=c-'0'; c=GetChar(); } return a; } int p[1050],b[1050]; int f[21][1050],ans[1050][1050]; void Divide(register int step,register int l,register int r) { if(l==r) { for(register int j=0;j<=1000;j++) ans[l][j]=f[step-1][j]; return; } register int mid=(l+r)>>1; for(register int j=0;j<=1000;j++) f[step][j]=f[step-1][j]; for(register int i=l;i<=mid;i++) for(register int j=1000;j>=p[i];j--) f[step][j]=_max(f[step][j],f[step][j-p[i]]+b[i]); Divide(step+1,mid+1,r); for(register int j=0;j<=1000;j++) f[step][j]=f[step-1][j]; for(register int i=mid+1;i<=r;i++) for(register int j=1000;j>=p[i];j--) f[step][j]=_max(f[step][j],f[step][j-p[i]]+b[i]); Divide(step+1,l,mid); } int main() { register int N=getnum(),Q=getnum(); for(register int i=1;i<=N;i++) p[i]=getnum(),b[i]=getnum(); Divide(1,1,N); for(register int q=1;q<=Q;q++) { register int a=getnum(),c=getnum(); //printf("%d\n",ans[a][c]); PrintNum(ans[a][c]);PutChar('\n'); } fwrite(pbuf,1,pp-pbuf,stdout); return 0; } |