CodeChef CookOff 2014 March 3-Palindromes
問題
長さnの数字のみからなる文字列が与えられる。
この数字の連続する部分文字列(ただしleading zeroがあってはならない)で、
回文かつ3の倍数であるものの個数を求めよ。
制約条件
n≦1000000
方針
s[i] = (Σa[i]) % 3とする
i桁目まで、
s[j] = 0となるjがいくつあるか
s[j] = 1となるjがいくつあるか
s[j] = 2となるjがいくつあるか
を累積和を使って求めておく。
Manacherのアルゴリズムで回文半径を求める。
回文半径rが求まったら、中心をiとすれば、[i - k, i + k](kはr以下)は全て回文なので、
この範囲で3の倍数になるkの個数を、累積和を見て数えればよい。
ソースコード
const int N = 1000100; int n, rad[2*N], sum[N][3], s[N]; char in[N]; //spaghetti sourceのManacher void build(char *text, int n) { int i, j, k; for (i = 0, j = 0; i < 2*n; i += k, j = max(j-k, 0)) { while (i-j >= 0 && i+j+1 < 2*n && text[(i-j)/2] == text[(i+j+1)/2]) ++j; rad[i] = j; for (k = 1; i-k >= 0 && rad[i]-k >= 0 && rad[i-k] != rad[i]-k; ++k) rad[i+k] = min(rad[i-k], rad[i]-k); } } int main(){ gets(in); n = strlen(in); rep(i, n){ s[i + 1] = s[i] + in[i] - '0'; s[i + 1] %= 3; rep(j, 3) sum[i + 1][j] = sum[i][j]; if(in[i] > '0') sum[i + 1][s[i + 1]]++; } build(in, n); ll ans = 0; rep(i, 2 * n){ int l = (i + 1) / 2; int r = l + (rad[i] + 1) / 2; int mod = i % 2 ? 0 : (in[i / 2] - '0') % 3; if(mod) mod = 3 - mod; mod += s[l]; mod %= 3; ans += sum[r][mod] - sum[l][mod]; } rep(i, n) if(in[i] == '0') ans++; cout << ans << endl; return 0; }