Epworth
2019-06-05 12:06:52
一道隐藏得十分隐蔽的状压
题意大致是给你一个01串,每次操作可以对规定区间长度的0,1进行交换。
问最少用多少次才能将串的每一位变为1。
妙处一
看到区间修改我们想到利用差分求解。
每一次操作都相当与对操作区间的每一位与一异或(
所以可以把原问题转变在差分数组
对
所以原问题转化为给你一个01串,每次操作可以将串中的两个
问最少花费多少才能将串的每一位变为
为什么要将差分数组全部变为
多举几个例子归纳一下可以发现全
妙处二
上面说到消去差分数组中两个
因为题目给我们限制了每次操作的区间长度,也就是
所以有些距离的两个
举个例子:
若操作的长度为
原串 | 差分数组 |
---|---|
1000 | 11000 |
0111 | 01001 |
1111 | 00000 |
距离为
所以我们要提前预处理出消去距离为
不妨将k个可执行的区间长度抽象为
用最小的物品来填充背包,背包的大小就是消去距离为
注意:不能将正负体积的物品放在一起DP,它们的转移顺序不同,要分两个写。
妙处三
利用背包处理完后,就可以愉快的状压
但是串的长度显然不符合状压的要求。
经过一番观察后我们发现最开始最多只有8个灯泡是熄灭的。
所以差分串中最多只有
好了,我们可以把所有的
对于一个状态,从左到右确定一个
最后输出
注意:我们是将差分串中的
#include<bits/stdc++.h>
#define maxn 40005
#define Epworth return 0
using namespace std;
int n,m,k,xx;
int dif[maxn];
int pos[maxn],tail;
int len[maxn],cost[maxn];
int f[1<<17];
int main(){
cin>>n>>m>>k;
for(int i=1;i<=m;i++) cin>>xx,dif[xx]^=1,dif[xx+1]^=1;
for(int i=1;i<=k;i++) cin>>len[i];
for(int i=1;i<=n+1;i++){
if(dif[i]) pos[++tail]=i;
}
memset(cost,0x3f,sizeof(cost));
cost[0]=0;
for(int i=1;i<=k;i++){
for(int j=len[i];j<=n;j++){
cost[j]=min(cost[j],cost[j-len[i]]+1);
}
}
for(int i=1;i<=k;i++){
for(int j=n-len[i];j>=0;j--){
cost[j]=min(cost[j],cost[j+len[i]]+1);
}
}
int all=(1<<tail)-1;
memset(f,0x3f,sizeof(f));
f[all]=0;
for(int i=all;i>=0;i--){
for(int j=1;j<=tail;j++){
if(!((1<<(j-1))&i)) continue;
for(int k=j+1;k<=tail;k++){
if(!((1<<(k-1))&i)) continue;
int x=~((~i)|(1<<(j-1))|(1<<(k-1)));
f[x]=min(f[x],f[i]+cost[pos[k]-pos[j]]);
}
}
}
cout<<f[0]<<endl;
Epworth;
}
好了,上面这个漏洞百出的代码已经可以AC这道题了。
但是对于某些数据,它是错误的。
下面让我们来批判一下上面的代码。
看看下面这组数据:
7 2 2
2 3
5 4
1001111 |
---|
1000000 |
1111110 |
1100000 |
1111111 |
不难发现它可以通过
但上面跑出来是
为什么呢?
因为在上面跑完全背包正负价值是分开跑的,结果是不完整的。
但实际上
110011(0) |
---|
111100(1) |
111111(0) |
理想的状态下我们在最后一个灯泡后虚构了一个假灯泡协助操作,最后把它熄灭了。
但这明显是不被允许的。
但如果是这样却又可以了。
6 2 2
2 3
5 3
在差分串中两个
所以在本题中
.......
经过一番思索,我们还是好好的