$[\ CF\ 17\ E\ ]\ Palisection$


\(\\​\)

\(Description\)


给定一个长度为\(N\)的小写字母串。问有多少对相交的回文子串\((\)包含也算相交\()\),答案对\(51123987\)取模。

  • \(N\in [1,2\times 10^6]\)

\(\\\)

\(Solution\)


  • 先考虑相交如何处理。因为相交既跟端点有关,又跟长度有关,回文子串数目众多不好处理。正难则反。假设总回文串个数是\(cnt\)个,如果两两都相交一共有\(\frac{cnt\times(cnt-1)}{2}\)对,所以答案就是\(\frac{cnt\times(cnt-1)}{2}-\)不相交回文子串对数。

  • 先考虑处理回文串个数回文串个数。在\(Manacher\)时可以直接求出,因为每一个长的回文串一定包含着若干个共同回文中心的回文子串,所以一个位置带来的回文串数就是这一位置的回文半径长度\((\)还原后\()\)

  • 在考虑处理不相交对数。统计以每一个位置为左端点和右端点的回文子串个数,根据端点处理。一个直接的想法是对每一个位置直接累加,右端点在它左边的子串数\(\times\)左端点在它右边的子串数,但这种方法显然会算重很多部分。考虑固定一侧,即确定左端点必须在这一位置,让这个子串数乘上右端点在它左边的子串数累加出来的答案,是补充不漏的,其中第二项做的时候可以前缀和处理。

  • 关于以每一个开始和结束的回文子串数量,每求出一个位置暴力累加所有子串复杂度显然过不了。考虑差分。因为新加入的子串开头的位置一定是一段连续的区间,从最左的端点一直到回文中心,而结束的也是同理。最后对两个数列分别求一下前缀和就好,注意跟上一条结合在一起,右端点的数列会在计算过程中求两遍前缀和,即最后得到的是原数列的前缀和。

\(\\\)

\(Code\)


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 2000010
#define R register
#define gc getchar
#define mod 51123987
using namespace std;
typedef long long ll;

char c,s[N<<1];
ll ans;
int n,p,mr,slen,l[N<<1],r[N<<1],len[N<<1];

inline void init(){
  scanf("%d",&n);
  while(!isalpha(c=gc()));
  s[1]=s[slen=3]='#'; s[2]=c;
  for(R int i=2;i<=n;++i){s[++slen]=gc();s[++slen]='#';}
  s[0]='['; s[slen+1]=']';
}

inline void manacher(){
  for(R int i=1,p=0,mr=0;i<=slen;++i){
    len[i]=(i>mr)?1:min(mr-i+1,len[(p<<1)-i]);
    while(s[i-len[i]]==s[i+len[i]]) ++len[i];
    if(i+len[i]-1>mr){mr=i+len[i]-1;p=i;}
    ++l[i-len[i]+1]; --l[i+1];
    ++r[i]; --r[i+len[i]];
    (ans+=(ll)(len[i]>>1))%=mod;
  }
  ans=(ans*(ans-1)/2)%mod;
}

int main(){
  init();
  manacher();
  for(R int i=1,sum=0;i<=slen;++i){
    l[i]+=l[i-1]; 
    r[i]+=r[i-1];
    if(i%2==0){
      ans=(ans-(ll)l[i]*sum%mod+mod)%mod; 
      (sum+=r[i])%=mod;
    }
  }
  printf("%lld\n",ans);
  return 0;
}

猜你喜欢

转载自www.cnblogs.com/SGCollin/p/9690121.html