题解 密码锁
题面
解析
这题确实有些难度。。
首先,暴力做法,就直接二分+bfs,
然而,时间,空间都会炸掉!!!。
因此,考虑优化。
step 1
首先,我们可以用差分的思想,
设最终达到的状态是全部为零。
那么初始时k个需要打开的开关就设为1,
因为锁的取反也可以表示为加1后模2,
所以用a[i]记录第i个锁异或第i-1个锁的状态,
这样,对于一个区间[l,r],我们只需要修改a[l]和a[r+1]就行了。
关于前面处理的代码(有一些是后面讲的):
while(T--){ ans=-1; cnt=0; memset(d,0x3f,sizeof(d)); memset(a,0,sizeof(a)); memset(size,0,sizeof(size)); memset(v,0,sizeof(v)); memset(num,0,sizeof(num)); n=read();k=read();m=read(); for(int i=1;i<=k;i++){ int x=read(); a[x]=1; } for(int i=1;i<=m;i++){ size[i]=read(); } for(int i=n+1;i;i--) a[i]^=a[i-1]; n++; for(int i=1;i<=n;i++){ if(a[i]) num[i]=++cnt; } for(int i=1;i<=n;i++){ if(a[i]) bfs(i); } memset(v,0,sizeof(v)); memset(f,0x3f,sizeof(f)); if(dp((1<<cnt)-1)==INF) puts("-1"); else printf("%d\n",dp((1<<cnt)-1)); }
step 2
之前我们已经讲到了,能用差分记录状态,
并且实际上,
因为只修改l和r+1,
所以就相当于l的状态向右移了(r-l+1)位(题目中就是size[i]),
那么,如果l和r+1都是0的话,就没必要修改了。
而且,如果状态1右移后的位置的状态也是1,
那么这两个1就会被消掉。
比如说,当前状态为:
1 1 1 0 1 1 1 1 1 0,
则差分后的数组为:
1 0 0 1 1 0 0 0 0 1 0 (注意,差分要记录到n+1)。
那么,如果我们把区间[1,3]取反,
则状态为:
0 0 0 0 1 1 1 1 1 0
差分数组就为:
0 0 0 0 1 0 0 0 0 1 0,
因此,问题转化为,用最少的步数消掉所有的1。
所以,首先我们可以用bfs求出每个1到其他各个1的最少步数,
具体看代码:
void bfs(int x){ memset(v,0,sizeof(v)); memset(dis,0x3f,sizeof(dis)); queue <int> que; dis[x]=0; que.push(x); v[x]=1; while(!que.empty()){ int z=que.front(); que.pop(); for(int i=1;i<=m;i++){ int k=z+size[i]; if(k>n||v[k]) continue; dis[k]=dis[z]+1; que.push(k),v[k]=1; } for(int i=1;i<=m;i++){ int k=z-size[i]; if(k<=0||v[k]) continue; dis[k]=dis[z]+1; que.push(k),v[k]=1; } } for(int i=1;i<=n;i++){ if(num[i]){ d[num[x]][num[i]]=dis[i]; } } return ; }
step 3
最后,我们就要求消掉所有点的最少步数了!
注意,费用流在这里似乎不行(因为有无解的情况)
但是,我们还可以用DP,
因为k<=10,所以最多有20个1,
因此从(1<<cnt)-1开始,
递归求解消掉其中两个点的状态的最小步数,
当状态为0时,就返回0就行了。
看代码吧:
int dp(int x){ if(!x) return 0; if(v[x]) return f[x]; v[x]=1; int start=0; for(int i=1;i<=cnt;i++){ if((x&(1<<(i-1)))){ if(!start) start=i; else if(d[start][i]!=INF) f[x]=min(f[x],dp(((x^(1<<(start-1)))^(1<<(i-1))))+d[start][i]); } } return f[x]; }
最后上全篇代码:
#include<bits/stdc++.h> using namespace std; inline int read(){ int sum=0,f=1;char ch=getchar(); while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0' && ch<='9'){sum=sum*10+ch-'0';ch=getchar();} return f*sum; } const int MAXN=1<<20; const int INF=0x3f3f3f3f; int T,n,m,k,tem,ans=-1,cnt=0; int num[100001],dis[100001]; int size[100001],a[100001],v[2000001]; int d[22][22],f[2000001]; void bfs(int x){ memset(v,0,sizeof(v)); memset(dis,0x3f,sizeof(dis)); queue <int> que; dis[x]=0; que.push(x); v[x]=1; while(!que.empty()){ int z=que.front(); que.pop(); for(int i=1;i<=m;i++){ int k=z+size[i]; if(k>n||v[k]) continue; dis[k]=dis[z]+1; que.push(k),v[k]=1; } for(int i=1;i<=m;i++){ int k=z-size[i]; if(k<=0||v[k]) continue; dis[k]=dis[z]+1; que.push(k),v[k]=1; } } for(int i=1;i<=n;i++){ if(num[i]){ d[num[x]][num[i]]=dis[i]; } } return ; } int dp(int x){ if(!x) return 0; if(v[x]) return f[x]; v[x]=1; int start=0; for(int i=1;i<=cnt;i++){ if((x&(1<<(i-1)))){ if(!start) start=i; else if(d[start][i]!=INF) f[x]=min(f[x],dp(((x^(1<<(start-1)))^(1<<(i-1))))+d[start][i]); } } return f[x]; } int main(){ // freopen("secret.in","r",stdin); // freopen("secret.out","w",stdout); T=read(); while(T--){ ans=-1; cnt=0; memset(d,0x3f,sizeof(d)); memset(a,0,sizeof(a)); memset(size,0,sizeof(size)); memset(v,0,sizeof(v)); memset(num,0,sizeof(num)); n=read();k=read();m=read(); for(int i=1;i<=k;i++){ int x=read(); a[x]=1; } for(int i=1;i<=m;i++){ size[i]=read(); } for(int i=n+1;i;i--) a[i]^=a[i-1]; n++; for(int i=1;i<=n;i++){ if(a[i]) num[i]=++cnt; } for(int i=1;i<=n;i++){ if(a[i]) bfs(i); } memset(v,0,sizeof(v)); memset(f,0x3f,sizeof(f)); if(dp((1<<cnt)-1)==INF) puts("-1"); else printf("%d\n",dp((1<<cnt)-1)); } return 0; }