acm-(好题、哈希技巧)2020 China Collegiate Programming Contest Qinhuangdao Site J. Jewel Splitting

题面
传送门
本题让求一个字符串在不同划分下的排列数,具体来说,给定一个 d , d = 1 , 2 , 3 , . . , n d,d=1,2,3,..,n d,d=1,2,3,..,n,将字符串划分为 d d d个长度相同的部分,剩下一个长度为 n % d n\%d n%d的子串,然后求出这 d d d个长度相同部分的不同排列方案数,假设这个答案是 a n s d ans_d ansd,则我们需要求出 A N S = ∑ i = 1 n a n s i ANS=\sum_{i=1}^nans_{i} ANS=i=1nansi

考虑如何求解 a n s d ans_d ansd,假设 t = n % d t=n\%d t=n%d,那么我们枚举长度为 t t t的子串(即不参加排列的那多余的部分)出现的位置,由于 t t t子串的左边和右边的串长都必须是 d d d的整数倍,故其实 t t t只可能出现在固定的一些位置上。

先考虑 t t t子串在最左边的位置的时候的情况。这时候的分布情况大致如下图所示:
图一
我们先求出此时的排列方案数,根据组合数学的公式,假设 n / d 串 n/d串 n/d中有 k k k种不同的串,对于第 i i i种串存在 c n t [ i ] cnt[i] cnt[i]个相同的串,那么总排列方案数为 ( ∑ i = 1 k c n t [ i ] ) ! c n t [ 1 ] ! c n t [ 2 ] ! . . . c n t [ k ] ! \frac{(\sum_{i=1}^kcnt[i])!}{cnt[1]!cnt[2]!...cnt[k]!} cnt[1]!cnt[2]!...cnt[k]!(i=1kcnt[i])!,于是用哈希表对不同类型的串做一个 1 ∼ k 1\sim k 1k的映射,这样就能得到每个 c n t cnt cnt的值,于是能够求出方案数了。

下面开始枚举 t t t,枚举不同位置的 t t t的过程可以看成是 t t t向右移动的过程,比如现在将 t t t向右移动到下一个位置,大概如下图所示:
图二
我们容易发现移动一步之后,只有两个 c n t cnt cnt的值会发生改变,于是我们不妨维护 f v = c n t [ 1 ] ! c n t [ 2 ] ! c n t [ 3 ] ! . . fv=cnt[1]!cnt[2]!cnt[3]!.. fv=cnt[1]!cnt[2]!cnt[3]!..的值,比如现在这一步移动之后,我们会改变 c n t [ 1 ] cnt[1] cnt[1]的值,使它减少 1 1 1,不过新加入的一个 n / d n/d n/d串(即最左边那个串)可能是之前没出现过的,根据哈希表来动态判断,根据不同的情况,增加一个 c n t cnt cnt或者对原有的 c n t cnt cnt 1 1 1即可,同时维护 f v fv fv的值,于是每移动一步 t t t串我们都能 O ( 1 ) O(1) O(1)得到当前的排列方案数,当 t t t串移动到原串末端的时候,我们就得到了 a n s d ans_d ansd的值。

不过本题还需要考虑方案的重复问题,在 t t t串移动的过程中,当前的所有串的分布情况可能再之前已经出现过了,也就是说,这这两种分布体现在 c n t cnt cnt上是一模一样的,故我们不需要计算此时的方案数,那如何判断当前 c n t cnt cnt的分布在之前出现过呢,我们考虑对整个 c n t cnt cnt的分布进行哈希,这里有两种哈希的方式,第一种是把 c n t cnt cnt数组看成一个字符串,那么按照字符串的哈希方式即可,注意一定要开双基数!!开双基数!!开双基数!!第二种方法则是利用异或来搞,能够降低时间消耗,而且不需要双基数(需要选择一个比较适合的基数,越大越好比较玄学),这时候只需要一个基数即可,假设它是 b a s e base base,那么异或的哈希函数就是 ( c n t [ 1 ] ⋅ b a s e 1 )   x o r   ( c n t [ 2 ] ⋅ b a s e 2 )   x o r   ( c n t [ 3 ] ⋅ b a s e 3 ) . . . (cnt[1]\cdot base^1)\,xor\,(cnt[2]\cdot base^2)\,xor\,(cnt[3]\cdot base^3)... (cnt[1]base1)xor(cnt[2]base2)xor(cnt[3]base3)...

除了这个关于 c n t cnt cnt的分布的哈希表需要手写以外,我们还需要手写前面的映射哈希,以及字符串哈希,本题总共涉及三个哈希类型,是一道关于哈希表的练手好题。

这里给出两份代码,两份代码的主要区别在于 c n t cnt cnt的分布的哈希表的哈希方式不同,第一种是普通字符串哈希(采用双基数),第二种是异或哈希(方法如前文所述)。

代码一:

char s[maxn];
int h1[maxn],h2[maxn],ib1[maxn],ib2[maxn],fac[maxn],fav[maxn],ANS=0;
const int p = 388211,b=19;
int hhs;
struct HASH{
	struct HAS{
		ll id=-1;
		int v=0;
	}h[p];
	vector<int>rec;
	void add(ll id,int v){
		int hs=id%p;
		while(~h[hs].id && (h[hs].id^id)){
			hs+=b;
			if(hs>=p)hs-=p;
		}
		if(h[hs].id==-1)rec.push_back(hs);
		hhs=hs;
		h[hs].id=id;
		h[hs].v=v;
	}
	void ad(int hs,ll id,int v){
		if(h[hs].id==-1)rec.push_back(hs);
		h[hs].v=v;
		h[hs].id=id;
	}
	int qry(ll id){
		int hs=id%p;
		while(~h[hs].id && (h[hs].id^id)){
			hs+=b;
			if(hs>=p)hs-=p;
		}
		hhs=hs;
		return (~h[hs].id)?h[hs].v:-1;
	}
	int qr(int hs){
		return h[hs].v;
	}
	void clear(){
		FOR(i,0,rec.size())h[rec[i]].id=-1;
		rec.clear();
	}
}has,hs;
 
void init(int n){
	ANS=0;
	int bb1=1,bb2=1;
	FOR(i,1,n+1){
		ib1[i]=1ll*ib1[i-1]*ib1[1]%mod;
		ib2[i]=1ll*ib2[i-1]*ib2[1]%mod;
		h1[i]=sum(1ll*s[i]*bb1%mod,h1[i-1]);
		h2[i]=sum(1ll*s[i]*bb2%mod,h2[i-1]);
		bb1=1ll*bb1*b1%mod;
		bb2=1ll*bb2*b2%mod;
	}
}
ll shash(int l,int r){
	ll ans1=1ll*sub(h1[r],h1[l-1])*ib1[l-1]%mod;
	ll ans2=1ll*sub(h2[r],h2[l-1])*ib2[l-1]%mod;
	return ans1<<32ll|ans2;
}
int tot=0,mark[maxn],cnt[maxn],bs[maxn],bs2[maxn];
const int base = 7,base2 = 11;
void work(int n,int t,int d){
	ll id;int x,fv=1,xv=0,xv2=0;
	tot=0;
	for(register int i=t+1;i<=n;i+=d){
		id=shash(i,i+d-1);
		if(~(x=has.qry(id))){
			mark[i]=x;
		}else{
			mark[i]=++tot;
			has.ad(hhs,id,tot);
		}
		cnt[mark[i]]++;
	}
	FOR(i,1,tot+1)fv=1ll*fv*fav[cnt[i]]%mod,add(xv,1ll*cnt[i]*bs[i]%mod),add(xv2,1ll*cnt[i]*bs2[i]%mod);
	hs.add(1ll*xv<<32ll|xv2,1);
	int nd=n/d;
	add(ANS,1ll*fac[nd]*fv%mod);
	if(!t){
		hs.clear();
		has.clear();
		memset(cnt,0,sizeof(int)*(tot+3));
		return;
	}
	for(register int i=1;i+d-1<=n;i+=d){
		id=shash(i,i+d-1);
		if(~(x=has.qry(id))){
			mark[i]=x;
		}else{
			mark[i]=++tot;
			has.ad(hhs,id,tot);
		}
		if(mark[i]!=mark[i+t]){
			fv=1ll*fv*fac[cnt[mark[i]]]%mod*fac[cnt[mark[i+t]]]%mod;
			dec(xv,sum(1ll*cnt[mark[i]]*bs[mark[i]]%mod,1ll*cnt[mark[i+t]]*bs[mark[i+t]]%mod));
			dec(xv2,sum(1ll*cnt[mark[i]]*bs2[mark[i]]%mod,1ll*cnt[mark[i+t]]*bs2[mark[i+t]]%mod));
			cnt[mark[i]]++;
			cnt[mark[i+t]]--;
			fv=1ll*fv*fav[cnt[mark[i]]]%mod*fav[cnt[mark[i+t]]]%mod;
			add(xv,sum(1ll*cnt[mark[i]]*bs[mark[i]]%mod,1ll*cnt[mark[i+t]]*bs[mark[i+t]]%mod));
			add(xv2,sum(1ll*cnt[mark[i]]*bs2[mark[i]]%mod,1ll*cnt[mark[i+t]]*bs2[mark[i+t]]%mod));
			if(hs.qry(1ll*xv<<32ll|xv2)==-1){
				add(ANS,1ll*fac[nd]*fv%mod);
				hs.ad(hhs,1ll*xv<<32ll|xv2,1);
			}
		}
	}
	FOR(i,1,tot+1)cnt[i]=0;
	hs.clear();
	has.clear();
}
int main(){
	int kase=0;
	fac[0]=bs[0]=bs2[0]=1;
	FOR(i,1,maxn)fac[i]=1ll*fac[i-1]*i%mod,bs[i]=1ll*bs[i-1]*base%mod,bs2[i]=1ll*bs2[i-1]*base2%mod;
	fav[0]=1;fav[maxn-1]=qpow(fac[maxn-1],mod-2,mod);
	ROF(i,maxn-2,1)fav[i]=1ll*fav[i+1]*(i+1)%mod;
	ib1[0]=ib2[0]=1;ib1[1]=qpow(b1,mod-2,mod),ib2[1]=qpow(b2,mod-2,mod);
	int t=rd(),c=0;
	while(t--){
		int n=rds(s+1);
		++c;
		init(n);
		FOR(i,1,n+1){
			work(n,n%i,i);
		}
		printf("Case #%d: %d\n",++kase,ANS);
	}
}

代码二:

char s[maxn];
int h1[maxn],h2[maxn],ib1[maxn],ib2[maxn],fac[maxn],fav[maxn],ANS=0;
const int p = 388211,b=19;
int hhs;
struct HASH{
	struct HAS{
		ll id=-1;
		int v=0;
	}h[p];
	vector<int>rec;
	void add(ll id,int v){
		int hs=id%p;
		while(~h[hs].id && (h[hs].id^id)){
			hs+=b;
			if(hs>=p)hs-=p;
		}
		if(h[hs].id==-1)rec.push_back(hs);
		hhs=hs;
		h[hs].id=id;
		h[hs].v=v;
	}
	void ad(int hs,ll id,int v){
		if(h[hs].id==-1)rec.push_back(hs);
		h[hs].v=v;
		h[hs].id=id;
	}
	int qry(ll id){
		int hs=id%p;
		while(~h[hs].id && (h[hs].id^id)){
			hs+=b;
			if(hs>=p)hs-=p;
		}
		hhs=hs;
		return (~h[hs].id)?h[hs].v:-1;
	}
	int qr(int hs){
		return h[hs].v;
	}
	void clear(){
		FOR(i,0,rec.size())h[rec[i]].id=-1;
		rec.clear();
	}
}has,hs;
 
void init(int n){
	ANS=0;
	int bb1=1,bb2=1;
	FOR(i,1,n+1){
		ib1[i]=1ll*ib1[i-1]*ib1[1]%mod;
		ib2[i]=1ll*ib2[i-1]*ib2[1]%mod;
		h1[i]=sum(1ll*s[i]*bb1%mod,h1[i-1]);
		h2[i]=sum(1ll*s[i]*bb2%mod,h2[i-1]);
		bb1=1ll*bb1*b1%mod;
		bb2=1ll*bb2*b2%mod;
	}
}
ll shash(int l,int r){
	ll ans1=1ll*sub(h1[r],h1[l-1])*ib1[l-1]%mod;
	ll ans2=1ll*sub(h2[r],h2[l-1])*ib2[l-1]%mod;
	return ans1<<32ll|ans2;
}
int tot=0,mark[maxn],cnt[maxn],bs[maxn],bs2[maxn];
const int base = 1e9+7,base2 = 19;
void work(int n,int t,int d){
	ll id,xv=0;int x,fv=1,xv2=0;
	tot=0;
	for(register int i=t+1;i<=n;i+=d){
		id=shash(i,i+d-1);
		if(~(x=has.qry(id))){
			mark[i]=x;
		}else{
			mark[i]=++tot;
			has.ad(hhs,id,tot);
		}
		cnt[mark[i]]++;
	}
	FOR(i,1,tot+1)fv=1ll*fv*fav[cnt[i]]%mod,xv^=1ll*cnt[i]*bs[i];
	hs.add(xv,1);
	int nd=n/d;
	add(ANS,1ll*fac[nd]*fv%mod);
	if(!t){
		hs.clear();
		has.clear();
		memset(cnt,0,sizeof(int)*(tot+3));
		return;
	}
	for(register int i=1;i+d-1<=n;i+=d){
		id=shash(i,i+d-1);
		if(~(x=has.qry(id))){
			mark[i]=x;
		}else{
			mark[i]=++tot;
			has.ad(hhs,id,tot);
		}
		if(mark[i]!=mark[i+t]){
			fv=1ll*fv*fac[cnt[mark[i]]]%mod*fac[cnt[mark[i+t]]]%mod;
			xv^=1ll*cnt[mark[i]]*bs[mark[i]]^1ll*cnt[mark[i+t]]*bs[mark[i+t]];
			cnt[mark[i]]++;
			cnt[mark[i+t]]--;
			fv=1ll*fv*fav[cnt[mark[i]]]%mod*fav[cnt[mark[i+t]]]%mod;
			xv^=1ll*cnt[mark[i]]*bs[mark[i]]^1ll*cnt[mark[i+t]]*bs[mark[i+t]];
			if(hs.qry(xv)==-1){
				add(ANS,1ll*fac[nd]*fv%mod);
				hs.ad(hhs,xv,1);
			}
		}
	}
	FOR(i,1,tot+1)cnt[i]=0;
	hs.clear();
	has.clear();
}
int main(){
	int kase=0;
	fac[0]=bs[0]=bs2[0]=1;
	FOR(i,1,maxn)fac[i]=1ll*fac[i-1]*i%mod,bs[i]=1ll*bs[i-1]*base%mod;
	fav[0]=1;fav[maxn-1]=qpow(fac[maxn-1],mod-2,mod);
	ROF(i,maxn-2,1)fav[i]=1ll*fav[i+1]*(i+1)%mod;
	ib1[0]=ib2[0]=1;ib1[1]=qpow(b1,mod-2,mod),ib2[1]=qpow(b2,mod-2,mod);
	int t=rd(),c=0;
	while(t--){
		int n=rds(s+1);
		++c;
		init(n);
		FOR(i,1,n+1){
			work(n,n%i,i);
		}
		printf("Case #%d: %d\n",++kase,ANS);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值