1. 程式人生 > >$[ CF 17 E ] Palisection$

$[ CF 17 E ] Palisection$

getchar gist 包含著 type rip nac += manacher i+1


\(\\?\)

\(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;
}

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