zoukankan      html  css  js  c++  java
  • 星空:差分,状压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
    ",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
    ",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
    ",dp[(1<<cnt)-1]);
    28 }
    没上Kb。1008B
  • 相关阅读:
    Unity ShaderLab学习总结
    shader内置变量
    关于Unity中Shader的内置值
    Canvas控件最后一蛋——CanvasGroup
    Unity中关于Device Filter的选择问题
    Unity中使用扩展方法解决foreach导致的GC
    如何精简Unity中使用的字体文件
    好插件让你事半功倍!【资源篇】
    BFC(块级格式化上下文)的作用及生产方法
    获取视频或者音频时长
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/11335411.html
Copyright © 2011-2022 走看看