hdu 4436 str2int (SAM)

给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;
}


你可能感兴趣的:(sam)