给n个只包含数字的字符串, 问这n个字符串能分解为多少种不同的数字, 求出这些数字的和mod2012
先把n个串用10连接起来, 然后构造sam。 然后计数的时候可以从拓扑序从小到大计数,(以前一直以为只能从大到小。。。), cnt[i]表示这个节点上有多少种数, sum[i]表示这个节点之前的能构成的数的和是多少,转移就是直接转移到儿子上就可以了。。。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mnx 200020
#define mod 2012
int ch[mnx << 1][12], par[mnx << 1], val[mnx << 1], sz;
int cnt[mnx << 1], sum[mnx << 1];
int root, lst;
int creat(int v) {
++sz;
val[sz] = v;
par[sz] = 0;
cnt[sz] = 0;
sum[sz] = 0;
memset(ch[sz], 0, sizeof(ch[sz]));
return sz;
}
void extend(int c) {
int p = lst;
int np = creat(val[p] + 1);
while(p && ch[p][c] == 0)
ch[p][c] = np, p = par[p];
if(!p) par[np] = root;
else {
int q = ch[p][c];
if(val[q] == val[p] + 1)
par[np] = q;
else {
int nq = creat(val[p] + 1);
memcpy(ch[nq], ch[q], sizeof(ch[q]));
par[nq] = par[q];
par[q] = nq;
par[np] = nq;
while(p && ch[p][c] == q)
ch[p][c] = nq, p = par[p];
}
}
lst = np;
}
int b[mnx << 1], d[mnx];
char s[mnx];
int n, len;
int main() {
while(scanf("%d", &n) != EOF) {
sz = 0;
root = lst = creat(0);
len = 0;
for(int i = 1; i <= n; ++i) {
scanf("%s", s);
len += strlen(s);
for(int j = 0; s[j]; ++j)
extend(s[j] - '0');
extend(10);
++len;
}
memset(d, 0, sizeof(d));
for(int i = 1; i <= sz; ++i) ++d[val[i]];
for(int i = 1; i <= len; ++i) d[i] += d[i - 1];
for(int i = 1; i <= sz; ++i) b[d[val[i]]--] = i;
memset(cnt, 0, sizeof(cnt));
memset(sum, 0, sizeof(sum));
cnt[1] = 1;
for(int i = 1; i <= sz; ++i) {
int u = b[i];
for(int j = 0; j < 10; ++j) {
if(u == 1 && j == 0) continue; //去除前导0的情况。
if(ch[u][j] == 0) continue;
int v = ch[u][j];
int add = (sum[u] * 10 + j * cnt[u]) % mod;
sum[v] = (sum[v] + add) % mod;
cnt[v] += cnt[u];
cnt[v] %= mod;
}
}
int ans = 0;
for(int i = 1; i <= sz; ++i)
ans = (ans + sum[i]) % mod;
printf("%d\n", ans);
}
return 0;
}