#6436. 「PKUSC2018」神仙的游戏

题目描述

小 D 和小 H 是两位神仙。他们经常在一起玩神仙才会玩的一些游戏,比如「口算一个 4 4 4 位数是不是完全平方数」。

今天他们发现了一种新的游戏:首先称 s s s 长度为 l e n \rm len len 的前缀成为 border 当且仅当 s [ 1 … len ] = s [ ∣ s ∣ − len + 1 … ∣ s ∣ ] s[1\dots \text {len} ] = s[|s|-\text {len} + 1\dots |s|] s[1len]=s[slen+1s] 。给出一个由 01? 组成的字符串 s s s, 将 s s s 中的问号用变成 01 替换,对每个 l e n \rm len len 口算是否存在替换问号的方案使得 s s s 长度为 l e n \rm len len 的前缀成为 border,把这个结果记做 f ( len ) ∈ { 0 , 1 } f(\text{len})\in \{0,1\} f(len){0,1} f ( len ) = 1 f(\text{len}) = 1 f(len)=1 如果 s s s 长度为 l e n \rm len len 的前缀能够成为 border,否则 f ( len ) = 0 f(\text{len}) = 0 f(len)=0

由于小 D 和小 H 是神仙,所以他们计算的 s s s 的长度很长,因此把计算的结果一一比对会花费很长的时间。为了方便比对,他们规定了一个校验值: ( f ( 1 ) × 1 2 )  xor  ( f ( 2 ) × 2 2 )  xor  ( f ( 3 ) × 3 2 )  xor  …  xor  ( f ( n ) × n 2 ) (f(1)\times 1^2)~\text{xor}~(f(2)\times 2^2)~\text{xor}~(f(3)\times 3^2)~\text{xor}~\dots~\text{xor}~(f(n)\times n^2) (f(1)×12) xor (f(2)×22) xor (f(3)×32) xor  xor (f(n)×n2) 来校验他们的答案是否相同。xor 表示按位异或。但是不巧,在某一次游戏中,他们口算出的校验值并不一样,他们希望你帮助他们来计算一个正确的校验值。当然,他们不强迫你口算,可以编程解决。

数据范围

∣ s ∣ ≤ 5 × 1 0 5 \lvert s \rvert \leq 5\times 10^5 s5×105

题解

我们知道,如果一个前缀长度为 l e n len len ,它能够成为 border 的前提是 s i = s i + n − l e n s_i=s_{i+n-len} si=si+nlen

所以如果 s i ≠ s i + x s_i \ne s_{i+x} si=si+x ,那对于 y ∣ x y|x yx ,长度为 n − y n-y ny 的前缀一定不是 border

上述式子下标的差是定值,于是我们可以把其中一项下标翻转就是和为定值的式子啦

于是我们设 f i = [ s i = = 0 ] , g i = [ s i = = 1 ] f_i=[s_i==0],g_i=[s_i==1] fi=[si==0],gi=[si==1] ,将 g g g 翻转后卷积,得到哪些x是有出现不相等的情况,对于每个n-y的前缀,我们只需要判断y的倍数是否有出现不相等的情况即可

效率: O ( n ( l o g n + l n n ) ) O(n(logn+lnn)) O(n(logn+lnn))

代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5,P=998244353;
int A[N],B[N],n,t=1,p,r[N],G[2]={3,(P+1)/3};
char s[N];long long ans;
int X(int x){return x>=P?x-P:x;}
int K(int x,int y){
	int z=1;
	for (;y;y>>=1,x=1ll*x*x%P)
		if (y&1) z=1ll*z*x%P;
	return z;
}
void Ntt(int *a,int o){
	for (int i=0;i<t;i++)
		if (i<r[i]) swap(a[i],a[r[i]]);
	for (int wn,i=1;i<t;i<<=1){
		wn=K(G[o],(P-1)/(i<<1));
		for (int x,y,j=0;j<t;j+=(i<<1))
			for (int w=1,k=0;k<i;k++,w=1ll*w*wn%P)
				x=a[j+k],y=1ll*w*a[i+j+k]%P,
				a[j+k]=X(x+y),a[i+j+k]=X(x-y+P);
	}
	if (o)
		for (int i=0,v=K(t,P-2);i<t;i++)
			a[i]=1ll*a[i]*v%P;
}
int main(){
	scanf("%s",s);n=strlen(s);
	for (;t<n+n;t<<=1,p++);
	for (int i=0;i<t;i++)
		r[i]=(r[i>>1]>>1)|((i&1)<<(p-1));
	for (int i=0;i<n;i++)
		A[i]=(s[i]==48),B[i]=(s[n-i-1]==49);
	Ntt(A,0);Ntt(B,0);
	for (int i=0;i<t;i++)
		A[i]=1ll*A[i]*B[i]%P;
	Ntt(A,1);ans=1ll*n*n;
	for (int i=1;i<n;i++){
		bool J=0;
		for (int j=i;j<n;j+=i)
			J|=(A[n-j-1] || A[n+j-1]);
		if (!J) ans^=1ll*(n-i)*(n-i);
	}
	cout<<ans<<endl;return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值