UVA1673
這道題可以用廣義後綴自動機,不過陳鋒老師給我們講了一個巧妙地方法,使得這道題可以用普通的後綴自動機做。
題目大意:
給出個完全由數字組成的字符串。計算將這個的字符串的所有子串轉換爲整數後先去重再求和的結果,輸出其模2012的餘數。也就是求其子串的所有本質不同的字符串的和。
預處理:
首先,我們可以將個字符串拼接起來,拼接的部位可以用一個特殊的分隔符隔開。比如這裏我用 :,因爲。我們將拼接好的字符串記作,這樣問題就轉換成求中所有不含分隔符的本質不同的子串的和。對建立後綴自動機。
定義:
爲從的初始狀態到狀態中的所有不含分隔符以及前導0的數量(根據題意,前導0去掉後算一個。比如01和1算作同一個子串)。
爲從初始狀態到狀態的所有合法路徑形成的數字之和。
轉移方程:
即v由u轉移而來。而關於合法轉移,只要在轉移時不向分隔符的方向走子串就不會出現分隔符;只要不再初始狀態往0走,就不會出現前導0。
即當前的狀態和爲上一狀態十進制進一位加上當前選擇的路徑*對應的合法路徑數量。
轉移方式:
怎樣保證方程中的在之前就更新過了呢?不難發現,由於都是由向下走一步得來,因此有:因此我們將所有狀態按排個序,從小到大更新即可。本題中的是連續且大量重複的,可以採用計數排序解決,不過快排也夠了。
AC代碼:
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
const int MAXN = 120000;
int n;
string S;
struct SAM {
int size, last;
struct Node {
int len = 0, link = 0;
int next[11];
void clear() {
len = link = 0;
memset(next, 0, sizeof(next));
}
} node[MAXN * 2];
void init() {
for (int i = 0; i < size; i++) {
node[i].clear();
}
node[0].link = -1;
size = 1;
last = 0;
}
void insert(char x) {
int ch = x - '0';
int cur = size++;
node[cur].len = node[last].len + 1;
int p = last;
while (p != -1 && !node[p].next[ch]) {
node[p].next[ch] = cur;
p = node[p].link;
}
if (p == -1) {
node[cur].link = 0;
}
else {
int q = node[p].next[ch];
if (node[p].len + 1 == node[q].len) {
node[cur].link = q;
}
else {
int clone = size++;
node[clone] = node[q];
node[clone].len = node[p].len + 1;
while (p != -1 && node[p].next[ch] == q) {
node[p].next[ch] = clone;
p = node[p].link;
}
node[q].link = node[cur].link = clone;
}
}
last = cur;
}
}sam;
int N, Size = 0;
int
Cnt[2 * MAXN],
SUM[2 * MAXN],
Order[2 * MAXN];
bool Input() {
if (scanf("%d", &N) == EOF) {
return false;
}
S.clear();
while (N--) {
string Temp;
cin >> Temp;
S.insert(S.end(), Temp.begin(), Temp.end());
S.push_back(':');
}
sam.init();
for (int i = 0; i < S.size(); ++i) {
sam.insert(S[i]);
}
return true;
}
constexpr static int mod = 2012;
int DP() {
for (int i = 0; i < sam.size; ++i) {
Order[i] = i;
}
//排序
sort(Order, Order + sam.size, [](const int&Left,const int&Right)->bool {
return sam.node[Left].len < sam.node[Right].len;
}
);
//空串合法
Cnt[0] = 1;
int&& Ans = 0;
for (int i = 0; i < sam.size; ++i) {
const int& u = Order[i];
//如果j是初始狀態,就不能往0走,否則就可以,這樣就可以去除前導0
for (int j = (u ? 0 : 1); j < 10; ++j) {
const int& v = sam.node[u].next[j];
if (sam.node[v].len) {
Cnt[v] += Cnt[u];
Cnt[v] %= mod;
SUM[v] += SUM[u] * 10 + j * Cnt[u];
SUM[v] %= mod;
}
}
Ans += SUM[u];
Ans %= mod;
}
return Ans;
}
int main() {
while (Input()) {
memset(Cnt, 0x0, sizeof(Cnt));
memset(SUM, 0x0, sizeof(SUM));
printf("%d\n", DP());
}
return 0;
}