最终得对抗自己

字符串算法复习(四)

上一篇: 字符串算法复习(三)

BZOJ 3676

题目大意:给你一个串, 对于这个串的一个回文子串, 称这个串的权值为这个串的长度乘以这个串的出现次数。
问最大的权值是多少。

解题报告

回文树?算了我先不学, 学一发后缀自动机倍增操作…
首先manacher的思路告诉我们本质不同的回文串只有N个…只有能让mxp+p[mxp]变大的回文串才是未出现过的, 否然可以通过mxp对称出现。
然后用后缀自动机倍增查询即可, 后缀自动机拓扑排序(用mxd桶排)后倍增可以快速获得某个子串对应的节点, 具体看代码。

代码

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXL = 300030;
namespace SAM {
    const int MAXD = MAXL*2;
    struct Node {
        int ch[26];
        int pre, mxd;
    } d[MAXD];
    int dtot = 1, lst = 1;
    int val[MAXD], pos[MAXD];
    inline void insert (int ch) {
        int u = lst, nu = ++dtot;
        d[lst = nu].mxd = d[u].mxd+1;
        val[nu] = 1, pos[d[nu].mxd] = nu;
        while(u && !d[u].ch[ch]) d[u].ch[ch] = nu, u = d[u].pre;
        if(!u) d[nu].pre = 1;
        else {
            int v = d[u].ch[ch];
            if(d[v].mxd == d[u].mxd+1) d[nu].pre = v;
            else {
                int nv = ++dtot;
                d[nv] = d[v];
                d[nv].mxd = d[u].mxd+1;
                d[v].pre = d[nu].pre = nv;
                while(u && d[u].ch[ch] == v) d[u].ch[ch] = nv, u = d[u].pre;
            }
        }
    }
    int seq[MAXD], buk[MAXD], fa[MAXD][20];
    inline void build () {
        for(int i = 1; i<=dtot; i++) ++ buk[d[i].mxd];
        for(int i = 1; i<=d[lst].mxd; i++) buk[i] += buk[i-1];
        for(int i = 1; i<=dtot; i++) seq[buk[d[i].mxd]--] = i;
        for(int i = dtot; i; i--) val[d[seq[i]].pre] += val[seq[i]], fa[i][0] = d[i].pre;
        for(int lvl = 1; lvl<20; lvl++)
            for(int i = 1; i<=dtot; i++)
                fa[i][lvl] = fa[fa[i][lvl-1]][lvl-1];
    }
    inline int getNode (int l, int r) {
        int u = pos[r];
        for(int i = 19; i>=0; i--)
            if(d[fa[u][i]].mxd >= r-l+1)
                u = fa[u][i];
        return u;
    }
    inline int query (int l, int r) {return val[getNode(l, r)];}
}
char str[MAXL], buf[MAXL*2];
int p[MAXL*2];
int main () {
    scanf("%s", str);
    int len = strlen(str);
    for(int i = 0; i<len; i++) SAM::insert(str[i]-'a');
    SAM :: build();
    for(int i = 0; i<len; i++)
        buf[i<<1] = '#', buf[(i<<1)+1] = str[i];
    buf[len<<1] = '#';
    LL ans = 0;
    int up = len<<1, mxp = 0;
    for(int i = 0; i<=up; i++) {
        if(mxp+p[mxp] >= i) p[i] = min(mxp+p[mxp]-i, p[mxp*2-i]);
        while(i-p[i]>=0 && buf[i-p[i]] == buf[i+p[i]]) ++ p[i];
        -- p[i];
        for(int j = mxp+p[mxp]+1; j<=i+p[i]; j++) {
            int lp = (i-(j-i))>>1, rp = (j-1)>>1;
            int cnt = SAM::query(lp+1, rp+1);
            ans = max(ans, (LL)(rp-lp+1)*cnt);
            //printf("at:[%d, %d]:%d\n", lp, rp, cnt);
        }
        if(i+p[i] > mxp+p[mxp]) mxp = i;
    }
    cout << ans << '\n';
}

解题报告(回文树版本)

PAM好有道理…把PAM搞出来就是个板题了..
几点注意:
1.构造只能沿着fail向上跳, 复杂度有保证(不会证明), 不能像AC自动机那么搞…
2.注意init中0为偶数点, fail指向奇数点…方便构造, 而且rec[0]一定要填一个失配字符…

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXL = 300030;
namespace PAM {
    const int MAXD = MAXL;
    struct Node {
        int ch[26], len, pre, cnt;
    } d[MAXD];
    int lst, len, dtot = 1;
    int rec[MAXL];
    void init () {
        rec[0] = -1;
        d[1].len = -1;
        d[0].pre = 1;
    }
    void insert (int ch) {
        rec[++len] = ch;
        while(rec[len] != rec[len-d[lst].len-1]) lst = d[lst].pre;
        if(!d[lst].ch[ch]) {
            int u = d[lst].ch[ch] = ++dtot;
            d[u].len = d[lst].len+2;
            int t = d[lst].pre;
            while(rec[len] != rec[len-d[t].len-1]) t = d[t].pre;
            if(d[t].ch[ch] != u) d[u].pre = d[t].ch[ch];
        }
        lst = d[lst].ch[ch];
        d[lst].cnt ++;
    }
    int buk[MAXL], seq[MAXL];
    void sumup () {
        for(int i = 2; i<=dtot; i++) ++ buk[d[i].len];
        for(int i = 1; i<=len; i++) buk[i] += buk[i-1];
        for(int i = 2; i<=dtot; i++) seq[buk[d[i].len]--] = i;
        for(int i = dtot-1; i; i--) d[d[seq[i]].pre].cnt += d[seq[i]].cnt;
    }
}
char str[MAXL];
int main () {
    scanf("%s", str);
    int len = strlen(str);
    PAM :: init();
    for(int i = 0; i<len; i++) PAM :: insert(str[i]-'a');
    PAM :: sumup();
    LL ans = 0;
    for(int i = 2; i<=PAM::dtot; i++)
        ans = max(ans, (LL)PAM::d[i].len*PAM::d[i].cnt);
    cout << ans << '\n';
}
//abacaba

BZOJ 3160

题目大意: 自己看…比较复杂

解题报告

首先这个回文匹配的模式和卷积很像。
字符只有两种, 比较套路地用fft处理字符串匹配, 构造多项式P[i]=str[i]-‘a’, 然后多项式平方就可以算出以每个位置为对称轴的失配数量了。
然后组合数学一下再用manacher去除不满足的串…一坨细节…
FFT几乎忘完了, 复习 个版又忘记IDFT后要除N…题目还是蛮不错的。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <cmath>
using namespace std;
typedef long long LL;
const int MAXL = 100010;
const int MOD  = 1000000007;
const double pi = acos(-1.0);
struct Complex {
    double real, img;
    Complex () {real = img = 0.0;}
    Complex (double a, double b):real(a), img(b) {}
    inline Complex operator + (const Complex & b) const
    {return Complex(real+b.real, img+b.img);}
    inline Complex operator - (const Complex & b) const
    {return Complex(real-b.real, img-b.img);}
    inline Complex operator * (const Complex & b) const
    {return Complex(real*b.real-img*b.img, real*b.img+img*b.real);}
};
inline int bitReverse (int num, int N) {
    int ret = 0, bit = 1;
    while(bit<N) {
        ret <<= 1;
        ret += (bool)(num&bit);
        bit <<= 1;
    }
    return ret;
}
Complex temp[MAXL*3];
void fft (Complex * A, int N, double t) {
    for(int i = 0; i<N; i++) temp[bitReverse(i, N)] = A[i];
    for(int lvl = 2; lvl<=N; lvl<<=1) {
        Complex wn(cos(2.0*pi/(double)lvl), t*sin(2.0*pi/(double)lvl)), t1, t2;
        for(int i = 0; i<N; i+=lvl) {
            Complex w(1.0, 0.0);
            for(int j = 0; j<(lvl>>1); j++, w=w*wn) {
                t1 = temp[i+j], t2 = w*temp[i+(lvl>>1)+j];
                temp[i+j] = t1+t2, temp[i+(lvl>>1)+j] = t1-t2;
            }
        }
    }
    for(int i = 0; i<N; i++) A[i] = temp[i];
}
inline int advPow (int a, int b) {
    int ret = 1;
    while(b) {
        if(b & 1) ret = (LL)ret*a%MOD;
        a = (LL)a*a%MOD;
        b >>= 1;
    }
    return ret;
}
char str[MAXL], buf[MAXL*2];
Complex P[MAXL*3];
int pre[MAXL], suf[MAXL], p[MAXL*2];
int cnts[MAXL*3];
int main () {
    scanf("%s", str);
    int len = strlen(str);
    int lvl = 1;
    while(lvl <= len*2) lvl<<=1;
    for(int i = 0; i<len; i++)
        P[i].real = str[i]-'a', pre[i] = str[i]-'a', suf[i] = str[i]-'a';
    for(int i = 1; i<len; i++) pre[i] += pre[i-1];
    for(int i = len-2; i>=0; i--) suf[i] += suf[i+1];
    fft(P, lvl, 1.0);
    for(int i = 0; i<lvl; i++)
        P[i] = P[i]*P[i];
    fft(P, lvl, -1.0);
    for(int i = 0; i<lvl; i++) cnts[i] = -2*round(P[i].real/(double)lvl);
    for(int i = 0; i<len; i++)
        cnts[i] += pre[i]*2;
    for(int i = len; i<(len<<1)-1; i++) // ?
        cnts[i] += suf[i-len+1]*2;
    for(int i = 0; i<(len<<1)-1; i++) {
        if(i&1) cnts[i] = min(((i>>1)+1)<<1, (len-(i>>1)-1)<<1)-cnts[i];// 夹缝
        else cnts[i] = min(((i>>1)<<1)+1, ((len-(i>>1)-1)<<1)+1)-cnts[i];
    }
    int ans = 0;
    for(int i = 0; i<(len<<1)-1; i++)
        if(i&1) ans = (ans+advPow(2, cnts[i]>>1)-1)%MOD;
        else ans = (ans+advPow(2, (cnts[i]>>1)+1)-1)%MOD;
    for(int i = 0; i<len; i++)
        buf[i<<1] = '#', buf[(i<<1)+1] = str[i];
    int up = len<<1, mxp = 0, sum = 0;
    buf[up] = '#';
    for(int i = 0; i<=up; i++) {
        if(mxp+p[mxp] > i) p[i] = min(mxp+p[mxp]-i, p[mxp*2-i]);
        while(i-p[i] >= 0 && buf[i-p[i]] == buf[i+p[i]]) ++ p[i];
        if(i+(--p[i]) > mxp+p[mxp]) mxp = i;
        if(i&1) sum += (p[i]+1)>>1;
        else sum += p[i]>>1;
        sum %= MOD;
    }
    printf("%d\n", (ans-sum+MOD)%MOD);
}
/*
abaabaa
*/

TSINSEN A1225

题目大意: 自己看…

解题报告

最近学了回文树, 本来准备再练练手, 但是…
算了manacher水过…忘记取模wa*3醉了

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXL = 1000010;
const int MOD  = 19930726;
char str[MAXL];
int p[MAXL];
LL sum[MAXL];
inline int advPow (int a, LL b) {
    int ret = 1;
    while(b) {
        if(b & 1) ret = (LL)ret*a%MOD;
        a = (LL)a*a%MOD;
        b >>= 1;
    }
    return ret;
}
int main () {
    int N; LL K;
    cin >> N >> K;
    scanf("%s", str);
    int mxp = 0;
    for(int i = 0; i<N; i++) {
        if(mxp+p[mxp] >= i) p[i] = min(p[mxp*2-i], mxp+p[mxp]-i);
        while(i-p[i]>=0 && str[i-p[i]] == str[i+p[i]]) ++ p[i];
        -- p[i];
        if(mxp+p[mxp] < i+p[i]) mxp = i;
        ++ sum[p[i]*2+1];
    }
    for(int i = N-1; i>=1; i--) sum[i] += sum[i+2];
    LL ans = 1;
    for(int i = N; i>=1; i--) {
        if(sum[i] >= K) {ans = ans*advPow(i, K)%MOD; K = 0; break;}
        else ans = ans*advPow(i, sum[i])%MOD, K -= sum[i];
    }
    if(K) puts("-1");
    else cout << ans << '\n';
}

CodeForces 17E

给你一个长度 n (1 ≤ n ≤ 2·10^6) 的只由小写字母组成的字符串s。
考虑s的所有连续且回文的子串集合P, 位置不同但内容相同的两个串算作不同。
问从P中选出两个串且他们在s中有公共位置的方法数有几个, 结果对51123987取余。

解题报告

想了好久怎么直接计算…看完cf的tutor发现自己是xx, 转换成总数-有多少对不重合的回文串。
不重复很好算的…可以通过差分处理每个位置开始/结束的回文串数量, 扫一遍就可以了。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <cmath>
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXL = 2000020;
const int MOD  = 51123987;
char str[MAXL], buf[MAXL*2];
int p[MAXL*2];
pair<int, int> inv[MAXL*2];
LL sumS[MAXL], sumF[MAXL];
int main () {
    int N;
    scanf("%d", &N);
    scanf("%s", str);
    for(int i = 0; i<N; i++)
        buf[i<<1] = '#', buf[(i<<1)+1] = str[i];
    int up = N<<1, mxp = 0;
    buf[up] = '#';
    int itot = 0;
    LL ans = 0;
    for(int i = 0; i<=up; i++) {
        if(mxp+p[mxp] >= i) p[i] = min(p[mxp*2-i], mxp+p[mxp]-i);
        while(i-p[i]>=0 && buf[i-p[i]] == buf[i+p[i]]) p[i]++;
        -- p[i];
        if(i+p[i]> mxp+p[mxp]) mxp = i;
        if(p[i]) inv[++itot] = make_pair(1+((i-p[i])>>1), 1+((i+p[i]-1)>>1));
    }
    for(int i = 1; i<=itot; i++) {
        int l = (inv[i].second-inv[i].first+2)>>1;
        sumS[inv[i].first]++;
        sumS[inv[i].first+l]--;
        if((inv[i].second-inv[i].first+1)&1) sumF[inv[i].first+l-1]++;
        else sumF[inv[i].first+l]++;
        sumF[inv[i].second+1]--;
        ans += (inv[i].second-inv[i].first+2)>>1;
    }
    if((ans-1)&1) ans = ans/2%MOD*((ans-1)%MOD)%MOD;
    else ans = ans%MOD*((ans-1)/2%MOD)%MOD;
    for(int i = 1; i<=N+1; i++) sumS[i] += sumS[i-1];
    for(int i = 1; i<=N+1; i++) sumF[i] += sumF[i-1];
    LL t = 0;
    for(int i = 1; i<=N; i++) {
        (ans -= sumS[i]%MOD*t%MOD) %= MOD;
        (t += sumF[i])%=MOD;
    }
    ans = (ans+MOD)%MOD;
    cout << ans << '\n';
}

UVALive 7041

题目大意: 给两个串A和B, 问有多少四元组(a, b, c, d)满足A[a-b] == B[c-d]并且A[a-b]是回文串。

解题报告

感觉好AC自动机啊, 但是这里多了一个回文串的要求。
对A串建立回文自动机, 然后再自动机上计算标记一通乱搞, 然后把B串喂给自动机就可以 了。
注意别炸int, 开long long。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXL = 200020;
namespace PAM {
    const int MAXD = MAXL;
    struct Node {
        int ch[26];
        int len, pre, cnt;
        LL csuf;
    } d[MAXD];
    int dtot = 1, lst, len;
    int rec[MAXL];
    void init () {
        memset(d, 0, sizeof(d[0])*(dtot+1));
        len = 0; dtot = 1;
        rec[0] = -1;
        d[1].len = -1;
        d[0].pre = 1;
        lst = 1;
    }
    void insert (int ch) {
        rec[++len] = ch;
        while(rec[len] != rec[len-d[lst].len-1]) lst = d[lst].pre;
        if(d[lst].ch[ch] == 0) {
            int u = d[lst].ch[ch] = ++dtot;
            d[u].len = d[lst].len+2;
            int t = d[lst].pre;
            while(rec[len] != rec[len-d[t].len-1]) t = d[t].pre;
            if(u != d[t].ch[ch]) d[u].pre = d[t].ch[ch];
        }
        d[d[lst].ch[ch]].cnt++;
        lst = d[lst].ch[ch];
    }
    int buk[MAXL], seq[MAXD];
    void setup () {
        for(int i = 1; i<=len; i++) buk[i] = 0;
        for(int i = 2; i<=dtot; i++) ++ buk[d[i].len];
        for(int i = 1; i<=len; i++) buk[i] += buk[i-1];
        for(int i = 2; i<=dtot; i++) seq[buk[d[i].len]--] = i;
        for(int i = dtot-1; i; i--) d[d[seq[i]].pre].cnt += d[seq[i]].cnt;
        for(int i = 1; i<dtot; i++) d[seq[i]].csuf = d[seq[i]].cnt+d[d[seq[i]].pre].csuf;
        d[0].cnt = d[0].csuf = 0;
    }
    LL feed (char * str) {
        int u = 1, p = 1;
        LL ans = 0;
        while(str[p] != '\0') {
            while(u != 1 && (str[p] != str[p-d[u].len-1] || !d[u].ch[str[p]-'a'])) u = d[u].pre;
            if(d[u].ch[str[p]-'a']) u = d[u].ch[str[p]-'a'];
            //否然呆在1
            ans += d[u].csuf;
            //printf("at:%d, %c\n", u, str[p]);
            ++ p;
        }
        return ans;
    }
}
char str1[MAXL], str2[MAXL];
int main () {
    int T;
    scanf("%d", &T);
    for(int cas = 1; cas <= T; cas ++) {
        PAM :: init();
        scanf("%s%s", str1, str2+1);
        char * prt = str1;
        while(*prt != '\0') PAM::insert(*prt-'a'), ++prt;
        PAM :: setup();
        printf("Case #%d: %lld\n", cas, PAM::feed(str2));
    }
}

字符串告一段落。

评论列表

  1. Pingback: 字符串算法复习(三) « Hineven!

Join the discussion

Your email address will not be published. Required fields are marked *