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;
    }
    
  • 相关阅读:
    SD卡测试
    测试人员可能会遇到的问题
    HDU 1024 Max Sum Plus Plus
    HDU 1176 免费馅饼
    HDU 1257 最少拦截系统
    HDU 1087 Super Jumping! Jumping! Jumping!
    poj 1328 Radar Installation
    poj 1753 Flip Game
    HDU 1003 Max Sum
    HDU 5592 ZYB's Premutation(BestCoder Round #65 C)
  • 原文地址:https://www.cnblogs.com/chdy/p/12957174.html
Copyright © 2011-2022 走看看