【CF932G】Palindrome Partition(回文樹,動態規划)


【CF932G】Palindrome Partition(回文樹,動態規划)

題面

CF
翻譯:
給定一個串,把串分為偶數段
假設分為了\(s1,s2,s3....sk\)
求,滿足\(s_1=s_k,s_2=s_{k-1}......\)的方案數

題解

反正我是不會做
基本就是照着\(laofu\)的打了一遍(laofu太強啦)

這題分成了兩個步驟
如果直接分\(k\)段我們是沒法直接判斷的
假設兩段\(s_i,s_{k-i+1}\)
因為\(s_i=s_{k-i+1}=x_1x_2.....x_j\)
假設\(s_i\)的開始位置為\(p\)
假設原串\(S\)的長度為\(n\)
\(s_i=S[p]S[p+1]....S[p+j-1]\)
\(s_{k-i+1}=S[n-j-p+1]S[n-j-p+2]...S[n-p+1]\)
對應相等的關系是
\(S[p]=S[n-p-j+1]\)
如果\(p=1\)考慮一下特殊情況
那么就是\(S[1]=S[n-j]\)
那么,如果我們有一個串\(S'\)
\(S'=S[1]S[n]S[2]S[n-2].....\)
那么,對應到上面的相等關系里面,
就是\(S'[1..j]\) 是一個回文串
其他的每一組對應關系也是如此
所以題目轉換成了:
告訴你了\(S'\)回答把\(S'\)分為若干個長度為偶數的回文串的方案數
(如果沒有搞清楚可以手玩一下)

這個就是一個裸的\(dp\)
\(f[i]\)表示\(S'[1..i]\)划分為若干長度為偶數的回文串的方案數
得到轉移方程:

\[f[i]=\sum_{j}f[j-1] \]

其中,\(S'[j,i]\)是回文串
那么,每一個\(j\)對應的位置相當於是\(S'[1..i]\)的回文后綴
構建出回文樹之后沿着\(last\)\(fail\)就可以得到所有的\(j\)的位置
然后我們就寫出來了一份代碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 1000100
#define MOD 1000000007
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
char ch[MAX],s[MAX];
int n,f[MAX];
struct Palindromic_Tree
{
	struct Node
	{
		int son[26];
		int ff,len;
	}t[MAX];
	int last,tot;
	void init()
	{
		t[tot=1].len=-1;
		t[0].ff=t[1].ff=1;
	}
	void extend(int c,int n,char *s)
	{
		int p=last;
		while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
		if(!t[p].son[c])
		{
			int v=++tot,k=t[p].ff;
			t[v].len=t[p].len+2;
			while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
			t[v].ff=t[k].son[c];
			t[p].son[c]=v;
		}
		last=t[p].son[c];
	}
}PT;
int main()
{
	PT.init();
	scanf("%s",ch+1);
	n=strlen(ch+1);
	if(n&1){puts("0");return 0;}
	for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
	reverse(&ch[1],&ch[n+1]);
	for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
	f[0]=1;
	for(int i=1;i<=n;++i)
	{
		PT.extend(s[i]-97,i,s);
		int p=PT.last;
		while(p!=1)
		{
			if(PT.t[p].len%2==0&&PT.t[p].len>0)f[i]=(f[i]+f[i-PT.t[p].len])%MOD;
			p=PT.t[p].ff;
		}
	}
	printf("%d\n",f[n]);
	return 0;
}

然后在\(CF\)上交一發:

這里寫圖片描述

看一看數據是啥呢?

這里寫圖片描述

如果我們像這份代碼一樣,沿着\(fail\)一路上跳
因為所有字符都相同
所以要跳\(O(n)\)
復雜度變為了\(O(n^2)\)
完美\(TLE\)

所以肯定就不能這么做。。。
然后我就不會了


假設對於某個位置,它對應的若干回文后綴

這里寫圖片描述

如果他們的長度\(>len/2\)
可以證明他們的長度等差

這里寫圖片描述

證明?根據回文串的性質,上面的那些圈圈都是相等的串
證畢

如果把這一些看成一組
每一組都至少要\(÷ 2\)
所以至多只會有\(log\)
這樣能夠保證復雜度,所以我們考慮能否一組一組轉移
\(g[x]\)表示這一組東西的和
因為一組不能直接在串中表示
所以用回文樹上的節點來表示
所以,\(g[x]\)表示從節點\(x\)開始,一直到縮短的值不再是當前這個等差的位置產生的貢獻

這里寫圖片描述

比如對於這一組,它產生的貢獻就是
\(g[x]=f[j1]+f[j2]+f[j3]\)
(最底下那個是原串)

好的,假設我們當前位置是\(i\)
也就是最底下的原串是\(S'[1..i]\)

最長的回文后綴,也就是當前回文樹上的\(last\)
\(p=last\)
因為是最長的一個,所以之前肯定不會計算過這個位置的值
所以\(g[p]=f[j_1]\)

然后往上面的那些回文后綴看

這里寫圖片描述

這些紅色的代表着關於下面的父親對稱的回文前綴
我們驚奇的發現:
它們的開始位置怎么就這么巧呢?
正好就是我要算的東西
也就是說\(g[p]+=g[p.fail]\)
當然,前提是\(p.fail\)還在這一組等差的串里面
然后\(p\)就跳到第一個不是它所在的等差的回文后綴的位置
所以\(f[i]=\sum_{p}g[p]\),前提是\(i\%2=0\)

最后,總結一下算法
對於給定的串\(S\)
把它變成\(S'=S[1]S[n]S[2]S[n-1].....\)
然后依次構建回文樹
每個節點要記錄一下它所在的這個等差的回文后綴在哪個節點結束
然后對於每次的\(last\)節點向上跳
跳到第一個不是自己這一段回文的位置
因為每次至少要\(÷2\),所以至多跳\(log\)
綜上,時間復雜度\(O(nlog)\)
空間復雜度\(O(n\sum)\)\(\sum\)代表字符集大小

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 1000100
#define MOD 1000000007
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
char ch[MAX],s[MAX];
int n,anc[MAX],diff[MAX];
int ans[MAX],f[MAX];
struct Palindromic_Tree
{
	struct Node
	{
		int son[26];
		int ff,len;
	}t[MAX];
	int last,tot;
	void init()
	{
		t[tot=1].len=-1;
		t[0].ff=t[1].ff=1;
		anc[0]=1;
	}
	void extend(int c,int n,char *s)
	{
		int p=last;
		while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
		if(!t[p].son[c])
		{
			int v=++tot,k=t[p].ff;
			t[v].len=t[p].len+2;
			while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
			t[v].ff=t[k].son[c];
			t[p].son[c]=v;
			diff[v]=t[v].len-t[t[v].ff].len;
			anc[v]=(diff[v]==diff[t[v].ff])?anc[t[v].ff]:t[v].ff;
		}
		last=t[p].son[c];
	}
}PT;
int main()
{
	PT.init();
	scanf("%s",ch+1);
	n=strlen(ch+1);
	if(n&1){puts("0");return 0;}
	for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
	reverse(&ch[1],&ch[n+1]);
	for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
	ans[0]=1;
	for(int i=1;i<=n;++i)
	{
		PT.extend(s[i]-97,i,s);
		for(int k=PT.last;k;k=anc[k])
		{
			f[k]=ans[i-PT.t[anc[k]].len-diff[k]];
			if(anc[k]!=PT.t[k].ff)
				f[k]=(f[k]+f[PT.t[k].ff])%MOD;
			if(!(i&1))ans[i]=(ans[i]+f[k])%MOD;
		}
	}
	printf("%d\n",ans[n]);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM