传送门
本题让求一个字符串在不同划分下的排列数,具体来说,给定一个
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
1∼k的映射,这样就能得到每个
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);
}
}