题目传送门
题意:给出一个字符串,将字符串中所有的回文子串全部放入一个集合里,去重后。问这个集合里有几对,使得a是b的子串。
思路:一开始想偏了,以为只要求每个回文串的回文后缀的数量加去掉开头结尾字符的数量就可以了。事实上,如果我们把去掉两个字符的字符串称为父节点,那么父节点的回文后缀和自己也能形成这样的pair。
设一个字符串x能产生的贡献为$dp[x]$,我们考虑x能产生的贡献是什么呢?
一个是和自己本身的所有回文后缀结合产生的贡献,这一个可以通过回文树上跳fail来完成。
一个是和自己的父节点结合,这个价值只有1,且要求父节点不是0或者1,当然自己也不能是0或者1.
还有一个是父节点能产生的贡献,这个就考虑到了父节点父节点和自己还有父节点的回文后缀和自己,此处可以通过dfs来得到。
一个子串可能同时是自己的祖先节点和回文后缀,这种情况要标记一下,不能重复计算,比如$aaa$。如果不停的掉fail,那么会跳到最后一个字符$a$,但$a$同时也是自己的父节点。
#pragma GCC optimize (2) #pragma G++ optimize (2) #pragma comment(linker, "/STACK:102400000,102400000") #include#include #include #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,b,a) for(int i=b;i>=a;i--) #define clr(a,b) memset(a,b,sizeof(a)) #define pb push_back #define pii pair using namespace std; typedef long long ll; ll rd() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int maxn=200010; char s[maxn]; ll dp[maxn]; int vis[maxn],cat=1; struct Palindromic_Tree { struct Node { int son[26]; int ff,len; }t[maxn]; int last,tot; ll ans=0; void init() { memset(t,0,sizeof(t[0])*(tot+1)); tot=last=ans=0; t[++tot].len=-1; t[0].ff=t[1].ff=1; } void extend(int c,int n) { 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]; } void dfs(int x,int fa){ ll cnt=0; int cx=t[x].ff; vis[x]++; while(cx!=0&&cx!=1&&vis[cx]==0){vis[cx]++;cnt++;cx=t[cx].ff; } dp[x]=cnt; if(x!=0&&x!=1&&fa!=0&&fa!=1)dp[x]=dp[fa]+cnt+1; ans+=dp[x]; rep(i,0,25){ if(t[x].son[i])dfs(t[x].son[i],x); } vis[x]--; cx=t[x].ff; while(cnt--){ vis[cx]--;cx=t[cx].ff; } } void solve(){ dfs(1,1),dfs(0,1); printf("Case #%d: %lld\n",cat++,ans); } }a; int main(){ int T; cin>>T; while(T--){ scanf("%s",s+1); int len=strlen(s+1); a.init(); rep(i,1,len){ a.extend(s[i]-'a',i); } a.solve(); } }