• [考试反思]1024csp-s模拟测试86:消耗


    %%%两个没素质的和一个萌两小时AK

    最近貌似总是可以比较快速的拿下T1,然后T2打到考试结束。。。

    T1是套路题没什么好说的。

    T2是一个比较蠢的博弈题,我花了很长时间干各种乱七八糟的事

    什么打表啊压表啊找规律啊找必胜策略啊。。。

    因为时间复杂度的计算错误导致我浪费了大量时间干没用的事

    最后发现刷表复杂度优秀就A了

    然后中途留了20分钟左右暴打T3,因为打成子序列了40->0

    而且更可惜的是其实已经很贴近正解思路了,就是没好好想好好打

    然后虽说最终名次看起来还不错,但是实际上和上面直接就是100分的断档。

    我这辈子什么时候能AK一回啊。。。

    及时而正确地计算复杂度,注意观察真正有用的状态数,实在不行跑一遍试试。

    看题。不管时间有多少,都要好好看题。

    T1:异或

    位运算类的套路题。二进制基本每次都是按位讨论。

    因为异或运算的特殊性,每一位之间的贡献互不影响。

    而只有当一个数是0另一个数是1时才会有值。

    这样问题就转化为了[L,R]内第k位上是1的有多少个。

    类似数位dp,直接求不好弄,我们用[0,R]的减去[0,L-1]的就是答案。

    从0开始的要好统计一些。

    可以发现某一位在二进制下的规律是00001111000011110000111100...

    第k位的循环节长度是1<<k+1,其中有1<<k位是1。然后剩余的部分就是不足1<<k+1位,

    在剩下的这么多位里1的个数是max(0,w%(1<<k+1)-(1<<k)+1)

    然后就没了。我不知道怎么数位dp做。

     1 #include<cstdio>
     2 #define mod 1000000007
     3 long long up(long long x){return x>0?x:0;}
     4 int main(){
     5     int t,l,r;long long ans=0,x;
     6     scanf("%d",&t);
     7     while(t--){
     8         scanf("%d%d",&l,&r);ans=0;
     9         for(int i=0;i<30;++i){
    10             x=r/(1<<i+1)-(l-1)/(1<<i+1)<<i;
    11             x+=up(r%(1<<i+1)-(1<<i)+1);
    12             x-=up((l-1)%(1<<i+1)-(1<<i)+1);
    13             (ans+=x*(r-l+1-x)%mod*(1<<i+1))%=mod;
    14         }
    15         printf("%lld
    ",ans);
    16     }
    17 }
    View Code

    T2:取石子

    博弈论?

    反正一个比较明显的结论就是

    如果你可以走向你个对手必败的局面,你就必胜,否则你必败。

    所以我们就用每一个必败状态去扩展,扩展得到所有的必胜状态。

    因为这道题里必败状态很少(10900个),所以刷表效率很高。

     1 #include<cstdio>
     2 bool x[301][301][301];
     3 int main(){
     4     for(int i=0;i<=300;++i)for(int j=i;j<=300;++j)for(int k=j;k<=300;++k)if(!x[i][j][k]){
     5         for(int s=1;s<=7;++s)for(int a=1;a<=300;++a){
     6             int I=i+(s&1?a:0),J=j+(s&2?a:0),K=k+(s&4?a:0);
     7             if(I>300||J>300||K>300)break;
     8             if(I>J)I^=J^=I^=J;
     9             if(I>K)I^=K^=I^=K;
    10             if(J>K)J^=K^=J^=K;
    11             x[I][J][K]=1;
    12         }
    13     }
    14     int t,a,b,c;scanf("%d",&t);
    15     while(t--){
    16         scanf("%d%d%d",&a,&b,&c);
    17         if(a>b)a^=b^=a^=b;
    18         if(a>c)a^=c^=a^=c;
    19         if(b>c)b^=c^=b^=c;
    20         puts(x[a][b][c]?"Yes":"No");
    21     }
    22 }
    View Code

    据说有人不知道什么叫刷表什么叫填表?

    如果你外层枚举ijk,内层枚举xyz

    刷表就是用dp[i][j][k]去更新dp[x][y][z]

    填表就是用dp[x][y][z]去更新dp[i][j][k]

    看似区别不大,但是在这道题里刷表是$O(10900M imes 7)$的,填表是$O(7 imes M^4)$的

    因为刷表能利用必败状态少的这个性质(外层枚举必败状态)

    而填表利用不了(外层什么都枚举,而内层去寻找必败状态)

    T3:优化

    一个常用的技巧就是遇到绝对值取最大值时,直接把绝对值去掉,正的负的都来一遍更新max,最后最优决策不会变差。

    这样的话我们考虑如何dp。

    相邻两段一定是一个加一个减。

    那么相邻三段一共就有4种状态,中间那一段的贡献分别是+2,0,0,-2

    然后用secret的思路设4种状态可做(听说还比较好做),而我写的是FACE的思路。

    一个$O(n^2k)$的思路是设dp[i][j][0/1]表示到位置i恰好是某一个段结尾,已经用了j个段,上一段是减是加。

    枚举ij,再枚举上一段的终点,找中间区间内最优的新区间起点。

    加一些ST表来O(1)得到最优决策就好了,特殊处理最后一段就好了。

    暴力就不细说了(稍懒,不然正解也写不完),直接给上代码吧。

     1 #include<cstdio>
     2 int max(int a,int b){return a>b?a:b;}
     3 int min(int a,int b){return a<b?a:b;}
     4 int n,k,dp[30004][202],dp2[30004][202],x[30004],sum[30004];
     5 int mn[40004][16],mx[40004][16],hi_bit[40004],ans;
     6 int qmax(int l,int r){
     7     int L=r-l+1,B=hi_bit[L];
     8     return max(mx[l][B],mx[r+1-(1<<B)][B]);
     9 }
    10 int qmin(int l,int r){
    11     int L=r-l+1,B=hi_bit[L];
    12     return min(mn[l][B],mx[r+1-(1<<B)][B]);
    13 }
    14 int bg(int l,int r){
    15     return sum[r]-qmin(l-1,r-1);
    16 }
    17 int sl(int l,int r){
    18     return sum[r]-qmax(l-1,r-1);
    19 }
    20 int main(){
    21     scanf("%d%d",&n,&k);
    22     for(int i=1;i<=n;++i)scanf("%d",&sum[i]),sum[i]+=sum[i-1],mn[i][0]=mx[i][0]=sum[i];
    23     for(int l=1;l<15;++l)for(int i=0;i<=n+1-(1<<l);++i)
    24         mn[i][l]=min(mn[i][l-1],mn[i+(1<<l-1)][l-1]),
    25         mx[i][l]=max(mx[i][l-1],mx[i+(1<<l-1)][l-1]);
    26     for(int l=0;l<15;++l)for(int j=1<<l;j<1<<l+1;++j)hi_bit[j]=l;
    27     for(int i=0;i<=n;++i)for(int j=0;j<=k;++j)dp[i][j]=dp2[i][j]=-1000000000;
    28     dp[0][0]=dp2[0][0]=0;
    29     for(int i=1;i<=n;++i)for(int j=1;j<=k;++j)for(int f=0;f<i;++f){
    30         dp[i][j]=max(dp[i][j],dp[f][j-1]+(f?0:1)*bg(f+1,i)),
    31         dp[i][j]=max(dp[i][j],dp2[f][j-1]+(f?2:1)*bg(f+1,i)),
    32         dp2[i][j]=max(dp2[i][j],dp2[f][j-1]-(f?0:1)*sl(f+1,i)),
    33         dp2[i][j]=max(dp2[i][j],dp[f][j-1]-(f?2:1)*sl(f+1,i));
    34     }
    35     for(int i=1;i<=n;++i)for(int f=0;f<i;++f)ans=max(ans,max(dp2[f][k-1]+bg(f+1,i),dp[f][k-1]-sl(f+1,i)));
    36     printf("%d
    ",ans);
    37 }
    T40

    但是“恰好”这个限制条件是我们思维常见的一个误区,其实“恰好”并没有意义。

    我们把它改成“至多/至少“的形式往往能让问题简单一些。

    在这道题里,我们就可以转化为上一段区间的右端点在i左边(含),这样的话其实并不影响我们的决策。

    有一个细节问题。就是最好在外层枚举k那一维(就叫它j了),不然会十分十分的麻烦。。。

    首先我们需要特殊处理j=1的转移,其实它的含义就是 到i为止的 最大的子区间 的前缀最大值。

    考虑具体做法,问题就是sum[i]-sum[p-1]。按照道理可能又需要枚举p了。

    但其实开一个变量存下sum到i之前时的最小值。这样最大减最小就能得到最大的。

    这样就可以得到dp[i][1][1]了,得到dp[i][1][0]其实是一样的就不细说了。

    然后接下来的转移会麻烦一点,我们考虑从j=x到j=x+1的转移。

    其中一个转移式子是这样的:

    dp[i][j][1]=dp[f][j-1][0]+2*(sum[i]-sum[f])

    这样的话怎么能不枚举f来弄?

    其实就和上面j=1的情况一样了,可以发现dp[f][j-1][1]-2sum[f]与i毫无关联,依旧是开一个变量维护前缀最大值,不断更新就好。

    其余的3个转移式子只需要分别维护一个变量就行了。大致同理。

    j=k的那一步转移依旧需要特殊处理。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int n,k,dp[2][30004][202],s[30004],ans,_01,_00,_11,_10;//0- 1+
     4 int main(){
     5     scanf("%d%d",&n,&k);
     6     for(int i=1;i<=n;++i)scanf("%d",&s[i]),s[i]+=s[i-1];
     7     memset(dp,0xa0,sizeof dp);
     8     for(int i=1;i<=n;++i)
     9         dp[1][i][1]=max(dp[1][i-1][1],_01+s[i]),dp[0][i][1]=max(dp[0][i-1][1],_10-s[i]),
    10         _10=max(_10,+s[i]),_01=max(_01,-s[i]);
    11     for(int j=2;j<k;++j){
    12         _01=_00=_11=_10=-1e9;
    13         for(int i=1;i<=n;++i)
    14             dp[1][i][j]=max(dp[1][i-1][j],max(_01+2*s[i],_11)),
    15             dp[0][i][j]=max(dp[0][i-1][j],max(_10-2*s[i],_00)),
    16             _10=max(_10,dp[1][i][j-1]+2*s[i]),_11=max(_11,dp[1][i][j-1]),
    17             _01=max(_01,dp[0][i][j-1]-2*s[i]),_00=max(_00,dp[0][i][j-1]);
    18     }
    19     _01=_00=_11=_10=-1e9;
    20     for(int i=1;i<=n;++i)
    21         ans=max(ans,_01+s[i]),ans=max(ans,_10-s[i]),
    22         _10=max(_10,dp[1][i][k-1]+s[i]),_01=max(_01,dp[0][i][k-1]-s[i]);
    23     printf("%d
    ",ans);
    24 }
    码倒不长。。
  • 相关阅读:
    路由器端口映射
    字符编码笔记:ASCII,Unicode和UTF-8
    2、Spring之AOP
    八、模板方法模式
    三、装饰者模式
    七、适配器模式
    六、命令模式
    五、单件模式
    乐观锁与悲观锁——解决并发问题
    一、策略模式
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/11736281.html
Copyright © 2020-2023  润新知