1. 程式人生 > 實用技巧 >題解 P1659 【[國家集訓隊]拉拉隊排練】

題解 P1659 【[國家集訓隊]拉拉隊排練】

一眼可得PAM

如果沒學過PAM的可以看這裡:PAM學習小結

我們令PAM上多記錄一個資訊\(sum\),表示該節點表示串在原串上出現了多少次。

當我們處理完了\(sum\),對於長度\(len\)為奇數的節點的資訊\(sum\)計入陣列\(a[i]\).

\(a[i]\)為長度為\(i\)的迴文子串出現次數。

\(a[i]\)降序排序後累加答案快速冪處理一下即可,不需太多點撥

重點來了

講一下怎麼處理\(sum\)

我們可以發現當一個節點\(u\)\(sum+1\),那麼\(fail[u]\)\(sum\)也要\(+1\)

熟悉AC自動機的OIer可以敏銳的察覺到可以用拓撲排序了(例如我

PAM的時候打個標記,最後統一一個拓撲排序向\(fail\)去更新\(sum\)即可

queue<int >q;		//in陣列為fail入邊數量
void tuopu(){
	for(int i=0;i<=tot;i++)if(in[i]==0)q.push(i);
	while(!q.empty()){
		int u=q.front();q.pop();
		sum[fail[u]]+=sum[u];in[fail[u]]--;
		if(in[fail[u]]==0)q.push(fail[u]);
	}
}

好像沒什麼問題,多一個拓撲排序就行了

但真的如此嗎?

我們觀察PAM

AC自動機的區別

AC自動機是建好\(Trie\)後再進行\(getFail\)的,\(fail\)的節點編號是會大於自身節點編號

PAM不會出現這種情況,PAM\(fail\)定義不同於AC自動機,構建使用增量法,保證了\(fail\)的節點編號一定小於自身節點編號。

所以就可以不用拓撲排序了,直接一個\(for\)從後到前更新即可

for(int i=tot;i>=0;i--)sum[fail[i]]+=sum[i];

總程式碼:

#include<bits/stdc++.h>
#define maxn 1010001
#define ll long long
#define mod 19930726
using namespace std;
char s[maxn];
int fail[maxn],len[maxn],trie[maxn][26],trans[maxn];
long long sum[maxn];
int per,slen,tot;
long long a[maxn],K,ans=1;
int getfail(int x,int i){
	while(i-len[x]-1<0||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
int gettrans(int x,int i){
	while(((len[x]+2)<<1)>len[tot]||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
void insert(int u,int i){
	int Fail=getfail(per,i);
	if(!trie[Fail][u]){
		len[++tot]=len[Fail]+2;
		fail[tot]=trie[getfail(fail[Fail],i)][u];
		trie[Fail][u]=tot;
		if(len[tot]<=2)trans[tot]=fail[tot];
		else{
			int Trans=gettrans(trans[Fail],i);
			trans[tot]=trie[Trans][u];
		}
	}
	per=trie[Fail][u];
	sum[per]++;		//記錄sum
}
ll qpow(ll n,ll m){
	ll ans=1ll;
	while(m){
		if(m&1){ans=ans*n;ans%=mod;}
		n=n*n;n%=mod;m>>=1;
	}return ans%mod;
}
int main(){
	scanf("%d%lld",&slen,&K);
	scanf("%s",s);
	fail[0]=1;len[1]=-1;tot=1;
	for(int i=0;i<slen;i++)insert(s[i]-'a',i);
	for(int i=tot;i>=1;i--)sum[fail[i]]+=sum[i];		//更新sum
	for(int i=2;i<=tot;i++)a[len[i]]+=sum[i],a[len[i]]%=mod;	//長度處理
	for(int i=slen;i>=1;i--){			//答案處理
		if(i%2==1){
			if(K>=a[i]){
				ans*=qpow(i,a[i]);ans%=mod;
				K-=a[i];
			}else{
				ans*=qpow(i,K);ans%=mod;
				K-=K;
				break;
			}
		}
	}
	if(K==0)			//判-1
	printf("%lld\n",ans%mod);
	else
	printf("-1\n");
	return 0;
}