zoukankan      html  css  js  c++  java
  • [NOI Online 2021 Ⅰ 提高组题解] 愤怒的小N

    题意简述

    数列 ({b_n}) 构造如下:(即 Thue-Morse 数列,OEIS A010060

    • (b_0=0)
    • (forall iin [2^n,2^{n+1}),b_i=1-b_{i-2^n})

    输入 (n) 和一个次数为 (m-1) 次的多项式 (f(x)=sumlimits_{i=0}^{m-1}a_ix^i) ,求 (sumlimits_{i=0}^{n-1}b_if(i)) ,答案对 (10^9+7) 取模。

    (0le log_2 nle 5cdot 10^5 , 1le mle 500 , 0le a_i< 10^9+7 , a_{m-1} eq 0)

    算法分析

    我们首先尝试找到 ({b_n}) 的通项,方便我们下面的求解。

    由构造过程,不难发现,我们在 (x) 的最高位前添上一个 (1) (设为 (y) ),则 (b_{y}=b_{x}mathrm{xor}1) ,这个过程中最明显的变化就是二进制中 (1) 的个数 (+1) ,因此我们有如下结论:

    [b_{n}=mathrm{popcount}(n) pmod 2 ]

    (mathrm{popcount}(n)) 即为 (n) 的二进制表示下有多少个 (1)

    但这个(pmod 2) 并不好处理,我们转化成乘积,即:

    [b_{n}=frac{1}{2}left(1-(-1)^{mathrm{popcount}(n)} ight) ]

    因此答案如下:

    [ans=frac{1}{2}sum_{i=0}^{n-1}left(1-(-1)^{mathrm{popcount}(i)} ight)f(i) ]

    稍作化简:

    [ans=frac{1}{2}sum_{i=0}^{n-1}f(i)-frac{1}{2}sum_{i=0}^{n-1}(-1)^{mathrm{popcount}(i)}f(i) ]

    由于 (f(x)) 是一个 (m-1) 次多项式,因此可以归纳证明 (f(x)) 的前缀和是一个 (m) 次多项式,用拉格朗日插值即可求出,时间复杂度 (O(m^2))

    现在我们只看后面一部分,即:

    [sum_{i=0}^{n-1}(-1)^{mathrm{popcount}(i)}f(i) ]

    (f(i)) 展开,交换求和顺序:

    [sum_{k=0}^{m-1}a_ksum_{i=0}^{n-1}(-1)^{mathrm{popcount}(i)}i^k ]

    我们只要能求出 (sumlimits_{i=0}^{n-1}(-1)^{mathrm{popcount}(i)}i^k),即可 (mathcal{O}(m)) 计算。

    考虑类似于 数位dp 的做法,([0,n)) 这个区间拆解为若干个形如 ([x,x+2^t)(x>2^t)) 的区间,这样我们所有的区间长度都是 (2) 的整次幂,方便我们计算。

    举个例子,(n=left(10101101 ight)_2),我们拆解为如下几个区间:

    • (left[left(00000000 ight)_2,left(10000000 ight)_2 ight))
    • (left[left(10000000 ight)_2,left(10100000 ight)_2 ight))
    • (left[left(10100000 ight)_2,left(10101000 ight)_2 ight))
    • (left[left(10101000 ight)_2,left(10101100 ight)_2 ight))
    • (left[left(10101100 ight)_2,left(10101101 ight)_2 ight))

    接下来我们的工作就是要计算 (sumlimits_{i=x}^{x+2^t-1}(-1)^{mathrm{popcount}(i)}i^k),不妨记此为 (f(x,t,k)),则:

    [f(x,t,k)=sum_{i=0}^{2^t-1}(-1)^{mathrm{popcount}(i+x)}(i+x)^k ]

    我们划分区间的优势现在就体现出来了,因为 (x>2^t) ,所以 (mathrm{lowbit}(x)>mathrm{highbit}(2^t)) ,因此 (mathrm{popcount}) 我们可以独立计算,即:

    [f(x,t,k)=(-1)^{mathrm{popcount}(x)}sum_{i=0}^{2^t-1}(-1)^{mathrm{popcount}(i)}(i+x)^k ]

    后面的 ((i+x)^k) 用二项式定理展开,即:

    [f(x,t,k)=(-1)^{mathrm{popcount}(x)}sum_{i=0}^{2^t-1}(-1)^{mathrm{popcount}(i)}sum_{j=0}^kdbinom{k}{j}i^jx^{k-j} ]

    继续交换求和顺序,并独立出 (x) ,即:

    [f(x,t,k)=(-1)^{mathrm{popcount}(x)}sum_{j=0}^kdbinom{k}{j}x^{k-j}sum_{i=0}^{2^t-1}(-1)^{mathrm{popcount}(i)}i^j ]

    子问题出现了,即:

    [f(x,t,k)=(-1)^{mathrm{popcount}(x)}sum_{j=0}^kdbinom{k}{j}x^{k-j}f(0,t,j) ]

    (f(0,t,j)) 并不能由我们上面的式子计算出,考虑 ([0,2^t)=[0,2^{t-1})cup[2^{t-1},2^t)),而这种区间的划分仍然满足上面的条件((x>2^t)),因此:

    [f(0,t,k)=f(0,t-1,k)+f(2^{t-1},t-1,k) ]

    综上,我们可以在 (mathcal{O}(m)) 的时间复杂度实现 (f(x,t,k)) 的转移,后面两维状态数是 (mathcal{O}(m^2)) 的,总共有 (mathcal{O}(log_2 n+m))个可能的(x),因此因此总复杂度为 (mathcal{O}((log_2n+m)m^2)) 的,可以通过 (60\%) 的数据。

    通过打表,发现 (f(x,t,k))(x>2^k) 或者 (t>k) 时都是 (0) 。这个也是可以归纳证明的,(但我不会证),此处略去。

    因此所有 (>m)(x) 都没有用,因此实际上只有 (mathcal{O}(m)) 个可能的不同的 (x) ,复杂度为 (mathcal{O}(log_2n+m^3)) ,可以通过。

    代码实现

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 1000005
    #define maxm 2000005
    #define inf 0x3f3f3f3f
    #define LL long long
    #define ull unsigned long long
    #define db double
    #define ldb long double
    #define mod 1000000007
    #define eps 1e-9
    #define local
    void file(string s){freopen((s+".in").c_str(),"r",stdin);freopen((s+".out").c_str(),"w",stdout);}
    template <typename Tp> void read(Tp &x){
    	int fh=1;char c=getchar();x=0;
    	while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
    }
    int ksm(int b,int p=mod-2,int Mod=mod){int ret=1;while(p){if(p&1)ret=1ll*ret*b%Mod;b=1ll*b*b%Mod;p>>=1;}return ret;}
    
    int iv2=((mod+1)/2);
    int n,m;
    char ss[maxn];
    int a[maxn];
    struct Larg{//拉格朗日插值求前缀和
    	int calc_f(int x){
    		int ret=0;
    		for(int i=m-1;i>=0;--i){
    			ret=(1ll*ret*x+a[i])%mod;
    		}
    		return ret;
    	}
    	int frac[505],frinv[505],xi[505],yi[505],syi[505],N;
    	int pv[505],sf[505];
    	void prepr(){
    		N=m+1;
    		frac[0]=1;for(int i=1;i<=N;++i)frac[i]=1ll*frac[i-1]*i%mod;
    		frinv[N]=ksm(frac[N]);for(int i=N;i;--i)frinv[i-1]=1ll*frinv[i]*i%mod;
    		yi[0]=calc_f(0);
    		for(int i=1;i<=N;++i){
    			xi[i]=i;yi[i]=(yi[i-1]+calc_f(i))%mod;
    			int prv=frinv[i-1],suf=frinv[N-i];
    			if((i^N)&1)suf=(mod-suf);
    			syi[i]=1ll*yi[i]*prv%mod*suf%mod;
    		}
    	}
    	int calc_sf(int x){
    		x%=mod;
    		pv[0]=sf[N+1]=1;
    		for(int i=1;i<=N;++i)pv[i]=1ll*pv[i-1]*(x-xi[i])%mod;
    		for(int i=N;i;--i)sf[i]=1ll*sf[i+1]*(x-xi[i])%mod;
    		int ret=0;
    		for(int i=1;i<=N;++i){
    			ret=(ret+1ll*syi[i]*pv[i-1]%mod*sf[i+1]%mod)%mod;
    		}
    		return (ret+mod)%mod;
    	}
    }La;
    
    int ans1,ans2;
    int C[505][505];
    int p2[maxn];
    void prep(){
    	for(int i=0;i<=m;++i){
    		C[i][0]=C[i][i]=1;
    		for(int j=1;j<i;++j){
    			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    		}
    	}
    	p2[0]=1;
    	for(int i=1;i<=n;++i)p2[i]=2ll*p2[i-1]%mod;
    }
    int ff[505][505];
    
    int calc(int tn,int k){//计算 f(0,tn,k)
    	if(tn>k)return 0;
    	if(tn==0)return k==0;
    	if(ff[tn][k]!=-1)return ff[tn][k];
    	int ret=calc(tn-1,k);
    	int X=p2[tn-1];
    	int px=1;
    	for(int j=k;j>=0;--j,px=1ll*px*X%mod){
    		ret=(ret+mod-1ll*C[k][j]*px%mod*calc(tn-1,j)%mod)%mod;
    	}
    	return ff[tn][k]=ret;
    }
    
    void solve(){
    	int tnm=0,tppc=0;
    	for(int i=0;i<n;++i){
    		int cnm=(2ll*tnm+ss[i]-'0')%mod,cppc=(tppc^(ss[i]-'0'));
    		if(n-i-1<=m){//无用的x直接剪枝减掉
    			if(ss[i]=='1'){//划分区间
    				int X=1ll*tnm*p2[n-i]%mod;
    				for(int k=0;k<m;++k){
    					int sm=0;
    					int px=1;
    					for(int j=k;j>=0;--j,px=1ll*px*X%mod){
    						sm=(sm+1ll*C[k][j]*px%mod*calc(n-i-1,j)%mod)%mod;
    					}
    					if(tppc)sm=(mod-sm);
    					ans2=(ans2+1ll*a[k]*sm%mod)%mod;
    				}
    			}	
    		}
    		
    		tnm=cnm,tppc=cppc;
    	}
    }
    
    signed main(){
    	#ifndef local
    		file("angry");
    	#endif
    	memset(ff,-1,sizeof(ff));
    	scanf("%s",ss);n=strlen(ss);
    	read(m);
    	for(int i=0;i<m;++i)read(a[i]);
    	La.prepr();prep();
    	int nm=0;
    	for(int i=0;i<n;++i)nm=(2ll*nm+ss[i]-'0')%mod;
    	nm=(nm+mod-1)%mod;
    	ans1=La.calc_sf(nm);
    	solve();
    	int ans=(ans1+mod-ans2)%mod;
    	ans=1ll*ans*iv2%mod;
    	printf("%d
    ",ans);
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 相关阅读:
    NOIP201208同余方程
    NOIP模拟赛 最佳组合
    NOIP模拟赛 拓展
    CF1253E Antenna Coverage(DP)
    LOJ6033「雅礼集训 2017 Day2」棋盘游戏 (博弈论,二分图,匈牙利算法)
    CF582E Boolean Function(DP,状态压缩,FMT)
    CF750G New Year and Binary Tree Paths(DP)
    Codeforces Round 596 题解
    AGC008E Next or Nextnext(组合计数,神奇思路)
    ARC082E ConvexScore(神奇思路)
  • 原文地址:https://www.cnblogs.com/ZigZagKmp/p/14586444.html
Copyright © 2011-2022 走看看