zoukankan      html  css  js  c++  java
  • luogu P6570 [NOI Online #3 提高组]优秀子序列 二进制 dp

    LINK:P6570 [NOI Online #3 提高组]优秀子序列

    Online 2的T3 容易很多 不过出于某种原因(时间不太够 浪了

    导致我连暴力的正解都没写.

    容易想到 f[i][j]表示前i个数 当前或为j的方案数.

    转移很简单 不过复杂度最坏是n*值域的.

    只有20 可以把状态降维 可以枚举子集来剪枝 这样就可以卡过40分了.

    容易发现当前为0的时候 整体状态要乘2这个可以打一个标记。

    这样在开o2的情况下就可以获得70分的好成绩了。

    const int MAXN=200010<<1,maxn=1000010;
    int n,maxx,m,top;
    int a[maxn],phi[MAXN];
    int p[MAXN],v[MAXN],mi[maxn];
    int f[MAXN];
    inline void prepare()
    {
    	m=1;while(m<=maxx)m=m<<1;
    	phi[1]=1;
    	rep(2,m,i)
    	{
    		if(!v[i])
    		{
    			p[++top]=i;
    			v[i]=i;
    			phi[i]=i-1;
    		}
    		rep(1,top,j)
    		{
    			if(m/p[j]<i)break;
    			v[i*p[j]]=p[j];
    			if(v[i]==p[j])
    			{
    				phi[i*p[j]]=phi[i]*p[j];
    				break;
    			}
    			phi[i*p[j]]=phi[i]*(p[j]-1);
    		}
    	}
    }
    int main()
    {
    	//freopen("sequence.in","r",stdin);
    	//freopen("sequence.out","w",stdout);
    	get(n);mi[0]=1;
    	rep(1,n,i)get(a[i]),maxx=max(maxx,a[i]),mi[i]=(ll)mi[i-1]*2%mod;
    	f[0]=1;prepare();--m;int flag=0;
    	rep(1,n,i)
    	{
    		if(!a[i]){++flag;continue;}
    		if(a[i])if(flag){rep(0,m,j)f[j]=(ll)f[j]*mi[flag]%mod;flag=0;}
    		int ww=a[i]^m;
    		for(int j=ww;j;j=ww&(j-1))f[j|a[i]]=add(f[j|a[i]],f[j]);
    		f[a[i]]=add(f[a[i]],f[0]);
    	}
    	int ans=0;
    	rep(0,m,j)ans=(ans+(ll)f[j]*mi[flag]%mod*phi[j+1])%mod;
    	put(ans);return 0;
    }
    

    考虑优化 这个状态转移显然是不可能再优化了。

    容易发现 j这个状态 和序列的顺序是无关的。

    换句话说 无论序列长什么样子 只要和原来的数集以及出现的次数对上方案数固定。

    这样可以直接利用值域来进行dp 脱离n的大小限制.

    容易想到 设f[i]表示状态i的方案数。

    这里可以刷表可以填表 不过填表比较清晰.

    想到枚举一个数字 j 然后 累加上 sum[j]*f[i^j] sum[j]表示j出现的次数.

    容易发现这样会计算重复 枚举j/i^j 刚好会重复 实际上 只需要枚举一半即可。

    关于证明也很好想 两边方案数对等 所以枚举一边即可。

    这样复杂度为3^18/2。

    不开o2就能过 令我难过的是 多写了一个mul函数和add函数就会直接T掉。

    所以函数的调用也浪费很多时间 这点值得注意 要减小自己的常数.

    const int MAXN=200010<<1,maxn=1000010;
    int n,maxx,m,top;
    int a[MAXN],phi[MAXN];
    int p[MAXN],v[MAXN],f[MAXN];
    inline int mul(int a,int b){return (ll)a*b%mod;}
    inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
    inline int mux(int a,int b){return a-b<0?a-b+mod:a-b;}
    inline void prepare()
    {
    	m=1;while(m<=maxx)m=m<<1;
    	phi[1]=1;
    	rep(2,m,i)
    	{
    		if(!v[i])
    		{
    			p[++top]=i;
    			v[i]=i;
    			phi[i]=i-1;
    		}
    		rep(1,top,j)
    		{
    			if(m/p[j]<i)break;
    			v[i*p[j]]=p[j];
    			if(v[i]==p[j])
    			{
    				phi[i*p[j]]=phi[i]*p[j];
    				break;
    			}
    			phi[i*p[j]]=phi[i]*(p[j]-1);
    		}
    	}
    }
    inline int ksm(int b,int p)
    {
    	int cnt=1;
    	while(p)
    	{
    		if(p&1)cnt=mul(cnt,b);
    		b=mul(b,b);p=p>>1;
    	}
    	return cnt;
    }
    int main()
    {
    	//freopen("1.in","r",stdin);
    	//freopen("sequence.out","w",stdout);
    	get(n);
    	rep(1,n,i){int get(x);++a[x],maxx=max(maxx,x);}
    	prepare();--m;f[0]=ksm(2,a[0]);
    	rep(1,m,i)
    	{
    		for(int j=i;j>(i^j);j=i&(j-1))
    			f[i]=(f[i]+(ll)f[i^j]*a[j])%mod;
    	}
    	int ans=0;
    	rep(0,m,j)ans=add(ans,mul(f[j],phi[j+1]));
    	put(ans);return 0;
    }
    
  • 相关阅读:
    使用VC++生成调试信息
    在Xp home上安装Rose 2003
    SkyDrive注册方法
    vsftpd同时使用系统用户和虚拟用户验证
    如何查看linux系统版本
    在RedHat AS中安装SVN
    Vnc & Gdm
    (转)如何:在设备上安装 SQL Server Compact 3.5
    java培训学习笔记一
    因为此版本的应用程序不支持其项目类型(.csproj),若要打开它,请使用支持此类型项
  • 原文地址:https://www.cnblogs.com/chdy/p/12957174.html
Copyright © 2011-2022 走看看