NOI Online 2021 提高组 愤怒的小N

P7468 [NOI Online 2021 提高组] 愤怒的小N

题目大意

N N N在玩一款游戏。这款游戏中有 n n n个关卡,分别为第 0 0 0关,第 1 1 1关, … \dots ,第 n − 1 n-1 n1关。这些关卡中有一部分是普通关卡,另一部分则是奖励关卡。

这款游戏中普通关卡与奖励关卡的分布比较特殊。如果用字符 a a a代表普通关卡,用字符 b b b代表奖励关卡,那么第 0 0 0关,第 1 1 1关, … \dots ,第 n − 1 n-1 n1关依次排列形成的字符串是一个无穷字符串 s s s的的前缀,且 s s s可以按照以下方式构造:

  1. 初始时 s s s为包含单个字符 a a a的字符串
  2. s s s的每个字符 a a a替换为 b b b,每个字符 b b b替换为 a a a得到字符串 t t t,然后将 t t t拼接到 s s s的末尾
  3. 不断执行操作 2 2 2,得到的字符串就是最终的 s s s

可以发现 s = a b b a b a a b b a a b a b b a ⋯ s=abbabaabbaababba\cdots s=abbabaabbaababba

通过第 i i i关可以得到 f ( i ) f(i) f(i)分,其中 f ( i ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a k − 1 x k − 1 f(i)=a_0+a_1x+a_2x^2+\cdots+a_{k-1}x^{k-1} f(i)=a0+a1x+a2x2++ak1xk1

N N N通过了所有奖励关卡,求他得到的分数模 1 0 9 + 7 10^9+7 109+7后的值。

为了方便, n n n以二进制的形式给出。

0 ≤ log ⁡ 2 n < 5 × 1 0 5 , 1 ≤ k ≤ 500 , 0 ≤ a i < 1 0 9 + 7 , a k − 1 ≠ 0 0\leq \log_2n<5\times 10^5,1\leq k\leq 500,0\leq a_i<10^9+7,a_{k-1}\neq 0 0log2n<5×105,1k500,0ai<109+7,ak1=0


题解

首先,我们发现,假设一个关卡的编号为 x x x,则如果去掉 x x x的最高位,则 x x x会从奖励关卡变为普通关卡或从普通关卡变为奖励关卡,所以当 x x x的二进制的各位之和为奇数时第 x x x关为奖励关卡,否则 x x x为普通关卡。

考虑 D P DP DP,设 f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1表示在编号为 0 ∼ 2 i − 1 0\sim 2^i-1 02i1的位置中,所有普通关卡/奖励关卡的编号的 j j j次方和。根据二项式展开可得转移式为

f i , j , v = f i − 1 , j , v + ∑ l = 0 j ( j l ) × f i − 1 , l , 1 − v × 2 ( i − 1 ) ( j − l ) f_{i,j,v}=f_{i-1,j,v}+\sum\limits_{l=0}^j\binom jl\times f_{i-1,l,1-v}\times 2^{(i-1)(j-l)} fi,j,v=fi1,j,v+l=0j(lj)×fi1,l,1v×2(i1)(jl)

在计算答案的时候,我们用类似树状数组的思想,按次数从小到大计算 n n n的二进制位中每个 1 1 1的贡献。设这一位为 i i i,枚举 f ( x ) f(x) f(x)中每一项,根据二项式展开用 f i , j , k f_{i,j,k} fi,j,k来得到贡献,那么答案为

a n s + = ∑ j = 0 k − 1 a j ∑ l = 0 j ( j l ) × f i , l , 1 − v × t m p j − l ans+=\sum\limits_{j=0}^{k-1}a_j\sum\limits_{l=0}^j\binom jl\times f_{i,l,1-v}\times tmp^{j-l} ans+=j=0k1ajl=0j(lj)×fi,l,1v×tmpjl

其中 v v v表示高于第 i i i位的 1 1 1的数量,这样能保证当前求的是奖励关卡。 t m p tmp tmp表示高于第 i i i位的部分转化为十进制后的值。

这样做是 O ( log ⁡ n k 2 ) O(\log nk^2) O(lognk2)的,我们考虑优化。

通过数学归纳法或者打表找规律,我们可以得到:

  • i > j i>j i>j f i , j , 0 = f i , j , 1 = ∑ t = 0 2 i − 1 t j 2 f_{i,j,0}=f_{i,j,1}=\dfrac{\sum\limits_{t=0}^{2^i-1}t^j}{2} fi,j,0=fi,j,1=2t=02i1tj

t m p tmp tmp表示高于第 k k k位的部分构成的数的值,则对于编号小于等于 t m p tmp tmp的奖励关卡,我们可以直接用 ∑ i = 0 t m p f ( i ) 2 \dfrac{\sum\limits_{i=0}^{tmp}f(i)}{2} 2i=0tmpf(i)来求。因为 f ( i ) f(i) f(i)是一个 k − 1 k-1 k1次的多项式,所以其前缀和也是一个 k − 1 k-1 k1次的多项式,用拉格朗日差值法即可 O ( k 2 ) O(k^2) O(k2)得到 ∑ i = 0 t m p f ( i ) \sum\limits_{i=0}^{tmp}f(i) i=0tmpf(i),再除以 2 2 2即可得到这部分的贡献。

对于剩下的部分,按前面所说的方法计算前 k − 1 k-1 k1位对答案的贡献,时间复杂度为 O ( log ⁡ n + k 3 ) O(\log n+k^3) O(logn+k3)

总时间复杂度为 O ( log ⁡ n + k 3 ) O(\log n+k^3) O(logn+k3)

code

#include
using namespace std;
const int N=500000,K=500;
const long long mod=1000000007,ny=500000004;
int n,k,c1;
long long ans=0,a[K+5],pw[K+5],x[K+5],y[K+5],C[K+5][K+5],f[K+5][K+5][2];
char c[N+5];
long long mi(long long t,long long v){
	if(!v) return 1;
	long long re=mi(t,v/2);
	re=re*re%mod;
	if(v&1) re=re*t%mod;
	return re;
}
void init(){
	C[0][0]=1;
	for(int i=1;i<=K;i++){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	long long now=0;
	for(int i=0;i<=K;i++){
		x[i]=i;y[i]=now;
		long long sum=0;
		for(int j=K-1;j>=0;j--) sum=(sum*i+a[j])%mod;
		now=(now+sum)%mod;
	}
}
long long gt(long long vx){
	long long re=0;
	for(int i=0;i<=K;i++){
		long long p=y[i],q=1;
		for(int j=0;j<=K;j++){
			if(i==j) continue;
			p=p*((vx-x[j]+mod)%mod)%mod;
			q=q*((x[i]-x[j]+mod)%mod)%mod;
		}
		re=(re+p*mi(q,mod-2)%mod)%mod;
	}
	return re;
}
int main()
{
//	freopen("angry.in","r",stdin);
//	freopen("angry.out","w",stdout);
	scanf("%s",c);
	c1=strlen(c);
	reverse(c,c+c1);
	scanf("%d",&k);
	for(int i=0;i<k;i++){
		scanf("%lld",&a[i]);
	}
	init();
	f[0][0][0]=1;pw[0]=1;
	for(int i=1,tmp=1;i<k;i++,tmp=tmp*2%mod){
		for(int j=1;j<k;j++) pw[j]=pw[j-1]*tmp%mod;
		for(int j=0;j<k;j++){
			f[i][j][0]=f[i-1][j][0];
			f[i][j][1]=f[i-1][j][1];
			for(int l=0;l<=j;l++){
				f[i][j][0]=(f[i][j][0]+C[j][l]*f[i-1][l][1]%mod*pw[j-l]%mod)%mod;
				f[i][j][1]=(f[i][j][1]+C[j][l]*f[i-1][l][0]%mod*pw[j-l]%mod)%mod;
			}
		}
	}
	for(int i=c1-1,tmp=0,w=0;i>=0;i--){
		if(c[i]=='0') continue;
		if(i<k){
			for(int j=1;j<k;j++) pw[j]=pw[j-1]*tmp%mod;
			for(int j=0;j<k;j++){
				long long sum=0;
				for(int l=0;l<=j;l++){
					sum=(sum+C[j][l]*f[i][l][w^1]%mod*pw[j-l]%mod)%mod;
				}
				ans=(ans+sum*a[j])%mod;
			}
		}
		tmp=(tmp+mi(2,i))%mod;w^=1;
	}
	long long tmp=0;
	for(int i=c1-1;i>=k;i--)
	if(c[i]=='1') tmp=(tmp+mi(2,i))%mod;
	ans=(ans+gt(tmp)*ny%mod)%mod;
	printf("%lld",ans);
	return 0;
}

你可能感兴趣的:(题解,好题,题解,c++)