【题解】[POI2004]PRZ
[POI2004]PRZ 解题报告;状态压缩DP
题意
将\(n\)名队员分成若干组,每名队员有重量\(w_i\)与过桥时间\(t_i\)。每组的总重量不超过\(W\)且该组对答案的贡献为组内队员中最大的\(t_i\),求全部\(n\)名队员分组能得到的最小贡献。
其中 \(100≤W≤400,1≤n≤16,1≤t_i≤50,10≤w_i≤100\)
思路
看到\(1≤n≤16\),于是八九不离十用状态压缩DP。定义状态i
为选择编号是\(i\)的二进制表示中为\(1\)的所有队员。
“这不是状压DP模板吗” 刚做完实打实模板题[USACO12MAR]Cows in a Skyscraper G的某人如是想。
但是奶牛题问的是最少分组数,而这一题问的是时间最短的安排。那么将每组对答案的贡献从\(1\)改为组里最大的\(t_i\)不就可以了?某人狂喜,欲套模板。但是如果能够成功把当前队员塞到现有的某组里,不能保证这一组的时间最优。
怎么办?
在奶牛题中,我们用一个一维DP数组记录答案,再用一个一维数组r[i]
,记录i
状态下的最大剩余空间以对“是否能把奶牛加入现有组里”进行约束。在这题中,我们用两个一维数组t
和w
,t[i]
和w[i]
分别记录选择i
状态的人为一组时的时间和总重量。最后以w
为约束,t
为贡献进行DP。
问题转化为“从若干组里挑若干组,每组交集为空。使所有人都被选,求最小贡献”。好耶ヽ(✿゚▽゚)ノ
枚举状态i
和它的子集j
,那么\(i=j+(i \oplus j)\),也即将状态i
视作 若干组合并后的结果\(j\) 和 单组\(i \oplus j\) 合并后得到的状态。那么f[i]=min(f[i],f[j]+w[i^j])
,当w[i^j]<=W
(是合法的一组).
代码实现
DP过程的内循环如果从1
~i
枚举j
,那么时间复杂度为\(O(2^{2n})\),会TLE两个点拿90分。
但是改变枚举方式就可以了!
实际操作时……
已知
-
用 \(j=i&(j-1)\) 作为\(j\)状态的枚举方式的合理性证明。
- 关于能够简化时间复杂度:由于只用枚举当前状态使置0的那一位的后面的情况,因此大大优化了状态枚举的常数
- 关于“-1”:只用使当前状态使置0的那一位的后面发生改变!♪(^∀^●)ノ
- 关于不会重复枚举:当不应该选的人被选时,会产生状态的重复。但是由于在转移状态时有\(&i\),因此不会选上不应该选的人。所以不可能枚举出不合法的状态。
-
\((i \oplus j)\oplus j=i\).
因此可以用j
和i^j
表示i
的“子集”。
AC code
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1<<16;
int W,n,T[18],c[18];
int f[N],t[N],w[N];
int main(){
scanf("%d%d",&W,&n);
for(int i=1;i<=n;i++) scanf("%d%d",&T[i],&c[i]);
int x;
for(int i=0;i<(1<<n);i++){
for(int j=1;j<=n;j++){
x=i+(1<<(j-1));
if(i&(x-i)) continue;
w[x]=w[i]+c[j];
t[x]=max(t[x],T[j]);
}
f[i]=INF;
}
f[0]=0;
for(int i=0;i<(1<<n);i++){
if(w[i]<=W) f[i]=t[i];//因为j=0时会退出下面的循环
for(int j=i;j;j=i&(j-1)){
if(w[i^j]<=W) f[i]=min(f[i],f[j]+t[i^j]);
}
}
printf("%d",f[(1<<n)-1]);
return 0;
}
有想法了会更新
安利一下我在博客园第一篇放错位置的题解