BZOJ3508 开灯 [校内NOIP2018模拟20181027] 密码锁
Time Limit: 10 Sec Memory Limit: 128 MB
Description
xx作为信息学界的大神,拥有众多的粉丝。为了感谢众粉丝的爱戴,xx决定举办一场晚会。为了气派,xx租了一个巨大的灯屏,这个灯屏有\(m\)行,每行有\(n\)个小灯泡。对于每一行灯,有L种操作方法,第i种表示你能将任意长度恰为\(A_i\)的连续一段灯泡的状态取反(灭变亮,亮变灭)。现对于每一行给定\(K\)个点,要求这K个点发光,其余点必须保持熄灭状态。求每一行达到目标状态的最小操作数。
Input
第一行一个数\(m\),表示LED屏的行数。
对于LED屏的每一行:
第一行为\(n,k,L\),意义见上。
第二行为\(k\)个数,表示要求发光的\(k\)个点。
第三行为\(L\)个数,表示\(L\)种操作方式。
Output
对于LED屏的每一行:如果无法达到目标状态,输出\(-1\),否则输出最少次数。
Sample Input
2 10 8 2 1 2 3 5 6 7 8 9 3 5 3 2 1 1 2 3Sample Output
2 -1HINT
对于\(100\%\)的数据,\(T\leq 10\),\(N\leq 10000\),\(K\leq 10\),\(L\leq 100\),\(1\leq A_i\leq N\)。
Source
By zjwst960422
Solution
一个很神仙的思路。
发现\(N\)非常大,但是\(K\)非常小,显然是状压DP,但是只状压\(K\)又不太好办。
于是我们发现,原来序列里只会有\(2K\)个点是一段\(0\)与一段\(1\)的间隔的点(我们这里取前一段的最后一个点)。然后我们又发现,不断地对一个段序列取反,实际上是让这一段和等长的只有\(1\)的序列异或。而这样之后,取反的区间内,相邻两个点的相对状态不会改变,即相邻两个点是否相等是不会改变的。
因此,我们对原序列\(a_i\)做一个这样的处理,维护这个点与后一个点的异或查分:
\[b[i]=a[i]\ xor\ a[i+1]\quad 0\leq i \leq n\]
这样的话,我们对\(a\)里面连续的一段(\(l..r\))取反,只会改变\(b\)里面的\(b[l-1]\)与\(b[r]\)两个点。
同时,\(a\)数组与\(b\)数组之间的又是唯一确定的关系。所以我们要\(A\)的末状态,等价于对应的\(B\)。
然后我们发现,如果把全\(0\)作为初状态,发光后的作为末状态,这样末状态太乱了,不方便转移。倒不如,倒过来,发光后的为初,全\(0\)为末。然后\(A\)全\(0\),对应的\(B\)也是全\(0\)的。
我们发现,我们实际上只是需要把初始的\(B\)里面的所有\(1\)全部去掉即可。然后,如果两个点坐标差恰好为一个操作时,就可以操作一次,那就是把这两点取反,中间的点不变!。那么我们要算出只取反\(i\)和\(j\) 需要的操作次数\(f[i][j]\),其实只需要从\(i\)出发跑BFS最短路即可。
然后考虑如何求出总的操作次数。
我们发现,\(B\)数列中最多有\(2K\) 个点为\(1\),所以我们只应该把那\(2K\)个点取反,其他点都不能动。那么我们状压一下这些点。然后就是一个非常显而易见的DP。\(dp[S]\)表示\(S\)里面的点已经完成了取反的任务。
\[dp[S]=\min\{dp[C_S{i,j}]+f[i][j]\ \ |\ \ i,j\in S\}\qquad dp[0]=0\]
然后我们类似于愤怒的小鸟的优化,这里会产生很多重复的转移,我们的\(i\)只需要取\(S\)中的最小的点就可以了。
最后答案为\(dp[full\_set]\)。
时间复杂度\(O(nmk+2^kk)\)
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<cstdlib> #define inf 1000000000 #define N 10005 #define M 2000005 #define T 45 using namespace std; inline int read() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x*=10;x+=ch-'0';ch=getchar();}return x*f; } int n,K,m,cnt; int x[N],size[N],a[N],num[N]; bool vis[N],mark[M]; int dis[N],d[25][25]; int q[N]; int f[M]; void bfs(int x) {memset(vis,0,sizeof(vis));int t=0,w=1;dis[x]=0;q[t]=x;vis[x]=1;while(t!=w){int now=q[t];t++;for(int i=1;i<=m;i++){if(now+size[i]<=n&&(!vis[now+size[i]])){vis[now+size[i]]=1;dis[now+size[i]]=dis[now]+1;q[w++]=now+size[i];}if(now-size[i]>0&&(!vis[now-size[i]])){vis[now-size[i]]=1;dis[now-size[i]]=dis[now]+1;q[w++]=now-size[i];}}}for(int i=1;i<=n;i++)if(num[i]){if(!vis[i])d[num[x]][num[i]]=inf;else d[num[x]][num[i]]=dis[i];} } int dp(int x) {if(!x)return 0;if(mark[x])return f[x];mark[x]=1;f[x]=inf;int st=0;for(int i=1;i<=cnt;i++){if(x&(1<<(i-1))){if(!st)st=i;else{if(d[st][i]!=inf)f[x]=min(f[x],dp(x^(1<<(st-1))^(1<<(i-1)))+d[st][i]);}}}return f[x]; } int main() {freopen("password.in","r",stdin);freopen("password.out","w",stdout);n=read();K=read();m=read();for(int i=1;i<=K;i++){x[i]=read();a[x[i]]=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);dp((1<<cnt)-1);if(f[(1<<cnt)-1]==inf)printf("-1");else printf("%d",f[(1<<cnt)-1]);return 0; }转载于:https://www.cnblogs.com/hankeke/p/BZOJ3508.html
总结
以上是生活随笔为你收集整理的BZOJ3508 开灯 [校内NOIP2018模拟20181027] 密码锁的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: SpringMVC拦截器HandlerI
- 下一篇: 【软件工程实践】结对项目-四则运算 “软