题目大意:
就是现在给出一个长度为n的字符串( 1 <= n <= 2*10^6 ), 求相交的回文字串的对数, 最终结果对51123987取模之后输出
大致思路:
继续刷manacher的论文题... 刚开始看到这个题的时候我想的是觉得可以正面做, 然后发现逗比了...因为我对于每一个以 i 位置为中心的半径为R[i]回文串只算了一次, 错以为可以树状数组弄一下...然后逗比地美国样例之后发现, 以 i 为中心的回文串有R[i]个( i 处不是 '#' 的情形下)...
然后看了下论文上的做法发现是求的相反的一面, 也就是求每一个回文串与其不想交的回文串的个数
然后就是多次前缀和来将复杂度降低到了O(n)...好吧前缀和真是强大..不过看到多重前缀和的做法之后表示有点也可以用来做正面的感觉...
论文上的做法是首先同BZOJ 2160一样先前缀和O(n)统计出所有的以输入的串的第 i 个字符结尾的回文串的数量S1[i]
然后S2[i] = S2[i - 1] + S1[i]作为S1的前缀和, 那么对于中心为i半径为[1~R[i]]的回文串来说, 处在这组回文串左边的的串的贡献就是S2中连续的一段和, 所以S3[i] = S3[i- 1] + S2[i]来表示S2的前缀和然后这一组回文串能找到的左边的不想交的回文串形成的对数是 S3[i] - S3[i - R[i] - 1] (当然在代码的实现当中我处理的串当中还是穿插着 '#' 所以在还原的原来的位置的时候写的有点长= =...或许这个东西能优化一下好些一点吧...
细节可以看代码...话说前缀和这东西真是强大
另外在学了Palindromic Tree 之后用Palindromic Tree写了一发这个题
代码如下:
Manacher 的做法
Result : Accepted Memory : 52844 KB Time : 404 ms
/*
* Author: Gatevin
* Created Time: 2015/3/20 19:02:12
* File Name: Chitoge_Kirisaki.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define maxn 2000010
const lint mod = 51123987LL;
char in[maxn];
char s[2*maxn];
int R[2*maxn];
void Manacher(char *s, int *R, int n)
{
int p = 0, mx = 0;
R[0] = 1;
for(int i = 1; i <= n; i++)
{
if(mx > i)
R[i] = min(R[2*p - i], mx - i);
else R[i] = 1;
while(s[i - R[i]] == s[i + R[i]])
R[i]++;
if(i + R[i] > mx)
mx = i + R[i], p = i;
}
return;
}
int n;
lint dp[maxn], S1[maxn];
int main()
{
scanf("%d", &n);
scanf("%s", in);
s[0] = '@';
for(int i = 0; i < n; i++)
s[2*i + 1] = in[i], s[2*i + 2] = '#';
s[2*n] = '$', s[2*n + 1] = '\0';
Manacher(s, R, 2*n);
for(int i = 1; i < 2*n; i++)
if(i & 1)//长度是奇数的回文串
{
dp[(i + 1) >> 1]++;
dp[((i + 1) >> 1) + ((R[i] - 1) >> 1) + 1]--;
}
else//长度为偶数的回文串
{
dp[(i >> 1) + 1]++;
dp[(i >> 1) + (R[i] >> 1) + 1]--;
}
lint all = 0;
for(int i = 1; i <= n; i++)//这里我dp和S1滚动数组了...节省内存
S1[i] = (S1[i - 1] + dp[i]) % mod, all = (all + S1[i]) % mod;
for(int i = 1; i <= n; i++)
dp[i] = (dp[i - 1] + S1[i]) % mod;
for(int i = 1; i <= n; i++)//就是第三重前缀和S3
S1[i] = (S1[i - 1] + dp[i]) % mod;
lint ans = 0;
for(int i = 2; i < 2*n; i++)
{
if(i & 1)
ans = (ans + S1[((i + 1) >> 1) - 1] - S1[((i + 1) >> 1) - ((R[i] - 1) >> 1) - 2] + mod) % mod;
else
ans = (ans + S1[(i >> 1) - 1] - S1[(i >> 1) - (R[i] >> 1) - 1] + mod) % mod;
}
printf("%I64d\n", (all*(all - 1) / 2 % mod - ans + mod) % mod);;
return 0;
}
Palindromic Tree的做法:
注意用next[26]会超内存限制...所以我改成了vector, 用时间弥补空间...
代码如下:
Result : Accepted Memory : 88156 KB Time : 560 ms
/*
* Author: Gatevin
* Created Time: 2015/3/31 10:34:41
* File Name: Rin_Tohsaka.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define maxn 2000010
const lint mod = 51123987LL;
char s[maxn];
int dp[maxn];
struct Palindromic_Tree
{
struct node
{
//int next[26];直接开next[26]的话会TLE, 用vector来代替(因为很多分支用不到)
vector<pair<int, int> > next;
int len;
int sufflink;
int cnt;
};
node tree[maxn];
int L, len, suff;
void newnode()
{
L++;
/*
for(int i = 0; i < 26; i++)
tree[L].next[i] = -1;
*/
tree[L].next.clear();
tree[L].len = tree[L].sufflink = tree[L].cnt = 0;
return;
}
void init()
{
L = 0, suff = 2;
newnode(), newnode();
tree[1].len = -1; tree[1].sufflink = 1;
tree[2].len = 0; tree[2].sufflink = 1;
return;
}
bool addLetter(int pos)
{
int cur = suff, curlen = 0;
int alp = s[pos] - 'a';
while(1)
{
curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos - 1 - curlen] == s[pos])
break;
cur = tree[cur].sufflink;
}
int find = -1;
for(unsigned int i = 0, sz = tree[cur].next.size(); i < sz; i++)
if(tree[cur].next[i].first == alp)
{
find = i;
break;
}
//if(tree[cur].next[alp] != -1)
if(find != -1)
{
//suff = tree[cur].next[alp];
suff = tree[cur].next[find].second;
return false;
}
newnode();
suff = L;
tree[L].len = tree[cur].len + 2;
//tree[cur].next[alp] = L;
tree[cur].next.push_back(make_pair(alp, L));
if(tree[L].len == 1)
{
tree[L].sufflink = 2;
tree[L].cnt = 1;
return true;
}
while(1)
{
cur = tree[cur].sufflink;
curlen = tree[cur].len;
if(pos - 1 - curlen >= 0 && s[pos - 1 - curlen] == s[pos])
{
//tree[L].sufflink = tree[cur].next[alp];
int to = -1;
for(unsigned int i = 0, sz = tree[cur].next.size(); i < sz; i++)
if(tree[cur].next[i].first == alp)
{
to = tree[cur].next[i].second;
break;
}
tree[L].sufflink = to;
break;
}
}
tree[L].cnt = 1 + tree[tree[L].sufflink].cnt;
return true;
}
};
Palindromic_Tree pal;
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
scanf("%s", s);
pal.init();
for(int i = 0; i < n; i++)
{
pal.addLetter(i);
dp[i] = pal.tree[pal.suff].cnt;//dp[i]表示以i位置结尾的回文串的数量
}
for(int i = 1; i < n; i++)
{
dp[i] = dp[i] + dp[i - 1];//现在dp[i]表示在i位置或之前结尾的回文串数量
if(dp[i] >= mod) dp[i] -= mod;
}
reverse(s, s + n);
pal.init();
lint ans = 0;
lint all = 0;//全部的回文串数量
for(int i = 0; i < n; i++)
{
pal.addLetter(i);
ans = (ans + (lint)pal.tree[pal.suff].cnt*(lint)dp[n - 1 - i - 1] % mod) % mod;
all = all + pal.tree[pal.suff].cnt;
if(all >= mod) all -= mod;
}
ans = ((all*(all - 1)) >> 1) % mod - ans + mod;//用全部的减去不相交的就行了
if(ans >= mod) ans -= mod;
printf("%I64d\n", ans);
}
return 0;
}