1. 程式人生 > 實用技巧 >Luogu P2375 [NOI2014]動物園|KMP+倍增+玄學

Luogu P2375 [NOI2014]動物園|KMP+倍增+玄學

題目連結

題目大意:
對於字串\(S\)的前\(i\)個字元構成的子串,既是它的字尾又是它的字首的字串中(它本身除外),最長的長度記作\(next[i]\)

現在求一個\(num\)陣列,定義為對於字串\(S\)的前\(i\)個字元構成的子串,既是它的字尾同時又是它的字首,並且該字尾與該字首不重疊,將這種字串的數量記作\(num[i]\)

輸出\(num_i+1\)的乘積。

題目思路:
sol 1:
既然題目都講到kmp了,那麼當然使用KMP打暴力啦。
將條件轉化為\(len*2<=i\)\(len\)為“對於字串\(S\)的前\(i\)個字元構成的子串,既是它的字尾又是它的字首的字串”的長度。
期望得分:50
sol 2:
不難發現,對於節點\(x\)

,若\(y\)是合法的,那麼y的fail也是合法的。
考慮KMP的過程中,將當前位置與\(next_i\)連邊,會形成一棵樹。那麼我們可以在這棵樹上通過樹上倍增找到最深的合法的節點,則該節點的父親均合法。
複雜度\(T \times NlogN\),應該能過,但T了。
加快讀並開O2/調換陣列維度即可。
程式碼:

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int t,f[1000100],fa[22][1000100],cnt[1000100],mi[21];char st[1000100];
int main()
{
	t=int(getchar()-'0');getchar();
	mi[0]=1;
	for (int i=1;i<=20;i++)
		mi[i]=mi[i-1]*2;
	for (int tt=1;tt<=t;tt++)
	{
		int len=1;
		st[1]=getchar();
		if (st[1]<'a'||st[1]>'z') {cout<<"1\n";continue;}
		f[1]=0;cnt[1]=0;
		for (int i=2,j=0;;i++)
		{
			len++;
			st[i]=getchar();
			if (st[i]<'a'||st[i]>'z') {len--;break;}
			while (st[i]!=st[j+1]&&j)
			{
				j=f[j];
			}
			if (st[i]==st[j+1]) f[i]=j+1,j++,cnt[i]=cnt[j]+1;else f[i]=0,cnt[i]=0;			
			bool flag=true;
			if (f[i]) fa[0][i]=f[i];else fa[0][i]=0,flag=false;
			for (int k=1;mi[k]<=i;k++)
			{
				fa[k][i]=fa[k-1][fa[k-1][i]];
			}
		}
		long long ans=1;
		for (int i=2,sm=0;i<=len;i++)
		{
			int s=1,k=i;
			if (i>mi[sm]) sm++;
			for (int j=sm;j>=0;j--)
			{
				if ((f[fa[j][k]]<<1)>i) k=fa[j][k];
			}
			if ((f[k]<<1)>i)
			{
				if ((f[fa[0][k]]<<1)<=i) k=fa[0][k]; else continue;
			}
			s+=cnt[k];
			ans*=s;
			ans%=mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

正解是用dfs,有興趣的可自行尋找資料。