星空:差分,状压dp
Posted $DeepinC$
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了星空:差分,状压dp相关的知识,希望对你有一定的参考价值。
总算不再是能用暴力卡常/随机化水过的好T3了。
说是打了两个标签,实际上最关键的是题意转化。
如果你丝毫不转化的话也可以:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int dp[2][1048577],b[65],k,n,m,x[9],f=1,mx; 4 int main(){ 5 scanf("%d%d%d",&n,&k,&m); 6 for(int i=1;i<=k;++i)scanf("%d",&x[i]); 7 for(int i=1;i<=m;++i)scanf("%d",&b[i]),mx=max(mx,b[i]); 8 if(mx<=16){ 9 memset(dp,0x3f,sizeof dp);dp[0][0]=0;const int maxst=(1<<mx-1)-1; 10 for(int i=1;i<=n;++i){ 11 int base=0;if(x[f]==i)f++,base=1;memset(dp[i&1],0x3f,sizeof dp[1]); 12 for(int j=0;j<=m;++j)if(i>=b[j])for(int st=0;st<=maxst;++st) 13 dp[i&1][st<<1^((1<<b[j])-1)^base]=min(dp[i&1][st<<1^((1<<b[j])-1)^base],dp[i&1^1][st]+(j?1:0)); 14 } 15 printf("%d\\n",dp[n&1][0]);return 0; 16 } 17 if(!k){puts("0");return 0;} 18 int bj=0; 19 for(int i=1;i<k;++i)if(x[i]+1!=x[i+1])bj=1; 20 if(!bj)for(int j=1;j<=m;++j)if(b[j]==k){puts("1");return 0;} 21 srand(time(0)+clock());printf("%d\\n",2+rand()%2); 22 }
(本来想上去讲讲的毕竟勉强算是个单题最高分但其实很水)
然而自然骗分并不稳定。考后再测:
考场上rp生效了!
这个暴力可以稍微讲一讲:
我们先观察数据范围可以发现k超级小但那是全部测试点是正解的事与我无瓜并不会用
其次m也不大但理由同上。
但是那个bi在某些测试点里小,也有些特别大。
和《奇怪的道路》有点莫名其妙的像?状压它!
只不过是把单点的操作换成了区间,其余真的没有什么区别。
时间复杂度O(nm2max(bi))。我不像题解一样只压了4而是尝试压了一下16。T了不要想了。
考场上还剩那么几分钟,干啥?骗分啊!
答案小于4。挺好。只有0123。
0好说啊,所有灯都亮着就是0啊。
1也好说啊,没亮的灯连成一片了而且操作里有能刚好这么长的就是1啊。
2得搜索吧,懒得打。rand一下。
效果不错。
好了好了废话太多说正解。
首先我们的操作是一次一个区间,暴力扫肯定T。区间异或怎么搞?
其实异或运算和加法在很多方面上有互通之处,如果是加法,你会怎么做?
差分啊!然后你就可以惊奇地发现异或的确也可以差分。
那么刚开始对于一个全亮的串,几个不亮的灯就是单点异或。也放进差分数组里处理。
每一个操作就是对于l~r区间异或。其实就是对于相隔距离一定的两个点同时异或一下。
我们的最终目标是得到一个全0串,差分数组全0就表示原区间全0,也就是灯都亮了。
我们继续考虑每一步操作,每次异或两个位置,如果它们都是0你异或完就都是1,和要消除1的目标不符且并不会i作出正贡献。
所以你只会同时选两个1或一个1一个0。前者会使两个1都消失。后者会让两者交换位置。
那么既然你想让它们都消失,就是不断改变1的位置最后让它们两两撞在一起消失掉。
而它每次会移动多少,就是向左右移动它给定的bi位啊。
跑bfs,找出所有1的单源最短路。(不建议打dijkstra或SPFA,常数大,而且就是说明你不理解这个bfs)
因为这个bfs每次走都是一次操作,相当于边权是1,队列里自带单调递增。
不要把边建出来,某牛T了。
然后我们就找出了每一对数字1对撞需要几次操作。
接下来状压它,不断枚举你要让哪两个1对消更新状态即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m,k,b[65],x[17],y[9],cnt,dt[40005],cost[17][17],q[40005],t,dp[1048577]; 4 int main(){y[0]=-1; 5 scanf("%d%d%d",&n,&k,&m); 6 for(int i=1;i<=k;++i)scanf("%d",&y[i]); 7 for(int i=1;i<=m;++i)scanf("%d",&b[i]); 8 for(int i=1;i<=k;++i){ 9 if(y[i]+1!=y[i+1])x[++cnt]=y[i]; 10 if(y[i]-1!=y[i-1])x[++cnt]=y[i]-1; 11 } 12 memset(cost,0x3f,sizeof cost); 13 for(int i=1;i<=cnt;++i){ 14 memset(dt,0x3f,sizeof dt);dt[q[t=1]=x[i]]=0; 15 for(int h=1;h<=t;++h)for(int j=1;j<=m;++j){ 16 if(q[h]+b[j]<=n&&dt[q[h]+b[j]]==0x3f3f3f3f)dt[q[++t]=q[h]+b[j]]=dt[q[h]]+1; 17 if(q[h]-b[j]>=0&&dt[q[h]-b[j]]==0x3f3f3f3f)dt[q[++t]=q[h]-b[j]]=dt[q[h]]+1; 18 } 19 for(int j=i+1;j<=cnt;++j)cost[i][j]=cost[j][i]=dt[x[j]]; 20 } 21 memset(dp,0x3f,sizeof dp);dp[0]=0; 22 for(int s=0;s<1<<cnt;++s)for(int i=1;i<=cnt;++i)if(!(s&1<<i-1)){ 23 for(int j=1;j<=cnt;++j)if(!(s&1<<j-1)) 24 dp[s|1<<i-1|1<<j-1]=min(dp[s|1<<i-1|1<<j-1],dp[s]+cost[i][j]); 25 break; 26 } 27 printf("%d\\n",dp[(1<<cnt)-1]); 28 }
以上是关于星空:差分,状压dp的主要内容,如果未能解决你的问题,请参考以下文章
HDU4057 Rescue the Rabbit(AC自动机+状压DP)