Educational Codeforces Round 70
E:
題意是給定 個字符串 ,對於任意組合 ,求所有組合在 中的出現次數之和。
因爲 和 是相連的,因此可以在 上枚舉連接點,然後求出以連接點右邊第一個點爲起始點有多少個 ,然後求出連接點左邊第一個點爲結束點有多少個 ,然後將這兩個值相乘就是該連接點的貢獻了。
因爲將所有字符串翻轉後,左邊第一個點結束點有多少個 個問題轉化爲右邊第一個點爲起始點有多少個 的問題,因此我們只要求後一個問題即可。
可以將所有字符串按長度分類,長度小於 的字符串放到字符串中,長度大於 的字符串通過哈希或者kmp計算。將 設爲 的根號長度後整體的複雜度就變成了 了。
除此之外,這個問題也是可以通過AC自動機來求的,複雜度就線性了。AC自動機在構建fail指針時需要將 ,因爲如果匹配到了 ,說明也匹配到了 。然後直接將 在AC自動機上掃,對於 ,對應的AC自動機上的狀態 就是 中所有爲 的後綴的數量。這裏就和根號做法的kmp很像,因爲AC自動機就是多模kmp。
根號分塊做法:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
const int B = 300;
using ll = long long;
string t;
int c[N], revc[N], nxt[N];
vector<string> ls, ss;
struct Trie {
int nxt[N][26], tot, rt, cnt[N];
void init() {
tot = 0;
rt = newnode();
}
int newnode() {
++tot;
memset(nxt[tot], 0, sizeof(nxt[tot]));
cnt[tot] = 0;
return tot;
}
void insert(const string& s) {
int now = rt;
for(int i=0; i<s.length(); ++i) {
int ch = s[i]-'a';
if(nxt[now][ch]==0) nxt[now][ch]=newnode();
now = nxt[now][ch];
}
++cnt[now];
}
int query(const string& t, int start) {
int res = 0;
int now = rt;
for(int i=start; i<t.length(); ++i) {
int ch = t[i]-'a';
now = nxt[now][ch];
if(now==0) break;
res += cnt[now];
}
return res;
}
}trie;
void kmp_pre(const string &s) {
int i,j;
j=nxt[0]=-1;
i=0;
while(i<s.length()) {
while(-1!=j&&s[i]!=s[j]) j=nxt[j];
nxt[++i]=++j;
}
}
void kmp(const string &s, int *c) {
int i=0, j=0, ans=0;
kmp_pre(s);
while(i<t.length()) {
while(-1!=j&&t[i]!=s[j]) j=nxt[j];
++i; ++j;
if(j>=s.length()) {
++c[i-s.length()];
j=nxt[j];
}
}
}
void cal(int *c) {
// puts("cal");
trie.init();
for(string &s : ss) {
trie.insert(s);
}
for(int i=0; i<t.length(); ++i) {
c[i]=trie.query(t, i);
// printf("c[%d]=%d\n", i, c[i]);
}
for(string &s : ls) {
kmp(s, c);
}
// for(int i=0; i<t.length(); ++i) {
// printf("c[%d]=%d\n", i, c[i]);
// }
}
int main() {
ios::sync_with_stdio(false);
cin >> t;
int n;
cin >> n;
for(int i=0; i<n; ++i) {
string s;
cin >> s;
if(s.length()<B) ss.push_back(s);
else ls.push_back(s);
}
cal(c);
reverse(t.begin(), t.end());
for(string &s : ls) reverse(s.begin(), s.end());
for(string &s : ss) reverse(s.begin(), s.end());
cal(revc);
ll ans = 0;
for(int i=1; i<t.length(); ++i) {
ans += 1LL*c[i]*revc[t.length()-i];
// printf("%d %d\n", i, t.length()-i);
}
cout << ans<<endl;
}
AC自動機做法:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
using ll = long long;
string t;
int c[N], revc[N], nxt[N];
vector<string> ls;
struct Trie {
int nxt[N][26], tot, rt, cnt[N], fail[N];
void init() {
tot = 0;
rt = newnode();
}
int newnode() {
++tot;
memset(nxt[tot], 0, sizeof(nxt[tot]));
cnt[tot] = 0;
return tot;
}
void insert(const string& s) {
int now = rt;
for(int i=0; i<s.length(); ++i) {
int ch = s[i]-'a';
if(nxt[now][ch]==0) nxt[now][ch]=newnode();
now = nxt[now][ch];
}
++cnt[now];
// printf("now: %d, c: %d\n", now, cnt[now]);
}
void build()
{
queue<int> q;
for(int i=0;i<26;++i)
{
if(nxt[rt][i]==0)
nxt[rt][i]=rt;
else
{
fail[nxt[rt][i]]=rt;
q.push(nxt[rt][i]);
}
}
while(!q.empty())
{
int now=q.front();q.pop();
cnt[now] += cnt[fail[now]];
for(int i=0;i<26;++i)
{
if(nxt[now][i]==0)
nxt[now][i]=nxt[fail[now]][i];
else
{
fail[nxt[now][i]]=nxt[fail[now]][i];
q.push(nxt[now][i]);
}
}
}
}
void solve(int *c) {
int now = rt;
for(int i=0; i<t.length(); ++i) {
int ch = t[i]-'a';
now = nxt[now][ch];
c[i] += cnt[now];
}
}
}trie;
void cal(int *c) {
// puts("cal");
trie.init();
for(string &s : ls) trie.insert(s);
trie.build();
// for(int i=1; i<=trie.tot; i++) printf("cnt[%d]=%d\n", i, trie.cnt[i]);
trie.solve(c);
// for(int i=0; i<t.length(); ++i) {
// printf("c[%d]=%d\n", i, c[i]);
// }
}
int main() {
ios::sync_with_stdio(false);
cin >> t;
int n;
cin >> n;
for(int i=0; i<n; ++i) {
string s;
cin >> s;
ls.push_back(s);
}
cal(c);
reverse(t.begin(), t.end());
for(string &s : ls) reverse(s.begin(), s.end());
cal(revc);
ll ans = 0;
for(int i=0; i+1<t.length(); ++i) {
ans += 1LL*c[i]*revc[t.length()-i-2];
// printf("%d %d\n", i, t.length()-i-2);
}
cout << ans<<endl;
}