星空:差分,状压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 }
考场上56分,直接状压最大的操作区间,再加骗分

(本来想上去讲讲的毕竟勉强算是个单题最高分但其实很水)

然而自然骗分并不稳定。考后再测:

考场上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 }
没上Kb。1008B

以上是关于星空:差分,状压dp的主要内容,如果未能解决你的问题,请参考以下文章

Luogu3943 星空 题解 状压+差分

noip模拟赛 星空

#12BZOJ3003LED BFS+状压DP

HDU4057 Rescue the Rabbit(AC自动机+状压DP)

18.06.03 POJ 4126:DNA 15年程设期末05(状压DP)

POJ1699 Best Sequence(AC自动机+状压DP)