zoukankan      html  css  js  c++  java
  • csps模拟86异或,取石子,优化题解

    题面:https://www.cnblogs.com/Juve/articles/11736440.html

    异或:

    考试时只想出了暴力

    我们可以对于二进制下每一位w,求出[l,r]中有几个数在这一位是1,记为x,设y表示[l,r]中有几个数在w位不是一

    这样就会有x×y对数在w位上产生贡献,每一对数会有2w的贡献,

    主要就是实现一个calc函数,calc(x,i)表示从0到x有多少的数二进制下第i位是1,然后我们发现一个规律:

    如果把0~9二进制打印出来:

    9001001
    8001000
    7000111
    6000110
    5000101
    4000100
    3000011
    2000010
    1000001
    0000000

    发现每一位是循环的,第0位循环节是2。第1位是4,第2位是8,而且只有没一个循环节的后一半是1,所以根据这个我们实现了calc函数

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 #define int long long
     6 #define re register
     7 using namespace std;
     8 const int mod=1e9+7;
     9 int t,l,r,ans;
    10 inline int q_pow(re int a,re int b,re int p){
    11     re int res=1;
    12     while(b){
    13         if(b&1) res=res*a%p;
    14         a=a*a%p;
    15         b>>=1;
    16     }
    17     return res;
    18 }
    19 int qpow(int a,int b){
    20     int res=1;
    21     while(b){
    22         if(b&1) res=res*a;
    23         a=a*a;
    24         b>>=1;
    25     }
    26     return res;
    27 }
    28 int calc(int x,int pos){
    29     ++x;
    30     int res=0;
    31     int tmp=qpow(2,pos+1);
    32     int q=x/tmp;
    33     res+=q*tmp/2;
    34     int p=x%tmp;
    35     if(p>tmp/2) res+=p-tmp/2;
    36     return res;
    37 }
    38 signed main(){
    39     scanf("%lld",&t);
    40     while(t--){
    41         scanf("%lld%lld",&l,&r);
    42         ans=0;
    43         for(int i=0;i<=31;++i){
    44             ans=(ans+(r-l+1-(calc(r,i)-calc(l-1,i)))*(calc(r,i)-calc(l-1,i))%mod*q_pow(2,i,mod)%mod)%mod;
    45         }
    46         printf("%lld
    ",2*ans%mod);
    47     }
    48     return 0;
    49 }
    View Code

    取石子:

    第一次做博弈论,然后我考试AC了。。。

    其实不能算是裸的博弈,毕竟我认为是dp

    一开始打搜索,发现不会打,打了2个多小时,然后突然发现可以dp筛出状态,然后打了正解,最后30分钟交上去AC了

    设g[i][j][k]表示三堆石子数量为i,j,k时能否先手必胜

    我们发现如果有x,y,z必输,那么x,y,z+k;

                  x+k,y,z;

                  x,y+k,z;

                  x+k,y+k,z;

                  x+k,y,z+k;

                  x,y+k,z+k;

                  x+k,y+k,z+k一定必胜

    因为先手可以通过x+k,y+k,z+k都拿走k,变成x,y,z从而让对手必输

    然后我们就可以转移了,从0,0,0开始,如果找到了一个必输的,那么用它更新后面必胜的,有些类似线性筛

    虽然有4层循环,但是一般不会进第4个循环,就像线性筛一样,总的复杂度还是O(n3)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define re register
    using namespace std;
    int t,x,y,z;
    bool g[305][305][305];
    inline void pre(){
    	for(re int i=0;i<=300;++i){
    		for(re int j=0;j<=300;++j){
    			for(re int k=0;k<=300;++k){
    				if(g[i][j][k]) continue;
    				for(re int p=1;p<=300;++p){
    					re int a=min(301,i+p),b=min(301,j+p),c=min(301,k+p);
    					if(a+b+c>=903) break;
    					g[a][j][k]=g[i][b][k]=g[i][j][c]=1;
    					g[a][b][k]=g[a][j][c]=g[i][b][c]=1;
    					g[a][b][c]=1;
    				}
    				break;
    			}
    		}
    	}
    }
    signed main(){
    	pre();
    	scanf("%d",&t);
    	while(t--){
    		scanf("%d%d%d",&x,&y,&z);
    		if(g[x][y][z]) puts("Yes");
    		else puts("No");
    	}
    	return 0;
    }
    

    优化:

    看到绝对值要想着去绝对值

    我们让每一个数都必选,那么每一个数a对整个答案的贡献可能是2a,-2a,a,-a,0

    a和-a之存在与第一段和最后一段,系数是2就是a所在的区间比它左右区间的元素的和都大,具体来说就是:

    2的情况:

    $|s_{i-1}-s_i|+|s_i-s_{i+1}|=2*s_i-s_{i-1}-s_{i+1}$,

    -2的情况和2的相反

    0的情况:

    $|s_{i-1}-s_i|+|s_i-s_{i+1}|=s_{i-1}-s_i+s_i-s_{i+1}$或$|s_{i-1}-s_i|+|s_i-s_{i+1}|=s_i-s_{i-1}-s_i+s_{i+1}$

    然后就可以dp转移了,设f[i][j][4]表示前i个,划分了j个区间的最大值,

    我们定义正为上升,负为下降,那么0表示上升,1表示下降,2表示从上升到下降,3表示从下降到上升

    然后狗shi的转移:

     1 if(j==1||j==k){
     2     f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][2])+a[i];
     3     f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][3])-a[i];
     4     f[i][j][2]=max(f[i-1][j][2],f[i][j][1]);
     5     f[i][j][3]=max(f[i-1][j][3],f[i][j][0]);
     6 }else{
     7     f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][2])+2*a[i];
     8     f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][3])-2*a[i];
     9     f[i][j][2]=max(f[i-1][j-1][2],max(f[i-1][j][2],f[i][j][1]));
    10     f[i][j][3]=max(f[i-1][j-1][3],max(f[i-1][j][3],f[i][j][0]));
    11 }

    其实挺好想的,考虑一下实际情况就好理解了,1和k的情况单独拿出来转移,因为他们的系数为1和-1

    最终答案就是max(f[n][k][2],f[n][k][3])

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define re register
    #define int long long
    using namespace std;
    const int MAXN=3e4+5;
    int n,k,a[MAXN],f[MAXN][205][4];
    signed main(){
    	scanf("%lld%lld",&n,&k);
    	for(re int i=1;i<=n;++i) scanf("%lld",&a[i]);
    	memset(f,-0x3f,sizeof(f));
    	for(int i=0;i<=n;++i)
    		for(int j=0;j<4;++j) f[i][0][j]=0;
    	for(int i=1;i<=n;++i){
    		int N=min(i,k);
    		for(int j=1;j<=N;++j){
    			if(j==1||j==k){
    				f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][2])+a[i];
    				f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][3])-a[i];
    				f[i][j][2]=max(f[i-1][j][2],f[i][j][1]);
    				f[i][j][3]=max(f[i-1][j][3],f[i][j][0]);
    			}else{
    				f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][2])+2*a[i];
    				f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][3])-2*a[i];
    				f[i][j][2]=max(f[i-1][j-1][2],max(f[i-1][j][2],f[i][j][1]));
    				f[i][j][3]=max(f[i-1][j-1][3],max(f[i-1][j][3],f[i][j][0]));
    			}
    		}
    	}
    	printf("%lld
    ",max(f[n][k][2],f[n][k][3]));
    	return 0;
    }
    
  • 相关阅读:
    Arch Linux中安装Anaconda
    Windows下使用Diskpart格式化U盘
    Jupyter Notebook的安装
    Docker的脚本安装
    pip无法正常使用卸载并重新安装
    Arch更新时failed to prepare transaction
    Privoxy将Socks代理转化HTTP代理
    Arch Linux下Visual Stdio Code在格式化C代码时报错
    GNOME 3.28 启用桌面图标
    Appium入门(8)__控件定位
  • 原文地址:https://www.cnblogs.com/Juve/p/11736711.html
Copyright © 2011-2022 走看看