YZOJ P2202 Legend VII – Ornament
时间限制:1000MS 内存限制:131072KB
难度:\(5.0\)
-
题目描述
-
输入格式
第一行有两个整数 \(N\) 和 \(Q\),表示商店有 \(N\) 个装饰品,一共有 \(Q\) 个询问。
第二行有 \(N\) 对整数,每 \(i\) 对整数 \(p_i, b_i\) 表示第 \(i\) 个装饰品的价格和好看度。
接下来 \(Q\) 行,每行两个整数 \(a, c\),分别描述 \(Q\) 个询问。
-
输出格式
对于每个询问输出一行,一个整数表示最大好看度。
-
样例输入
1 2 3 4 5 6 7 |
3 5 2 3 1 3 1 2 1 2 1 1 3 1 3 2 3 3 |
-
样例输出
1 2 3 4 5 |
5 3 3 3 6 |
-
数据规模与约定
对于 \(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; } |