2018北京icpc区域赛 H - Approximate Matching AC自动机

H - Approximate Matching

题意:有一个长度为n的01母串,求有多少个长度为m的01串T,使得母串是该串近似的子串。a是b的近似子串:那么b存在一个连续子串c,a与c至多只有一个字符不同
解法:我们先把母串和所有与其只有一个字符不同的01串插入字典树,建立AC自动机,预处理从 u u u节点出发,有 d [ u ] d[u] d[u]条长度为n的路径刚好走完才走到叶子节点,然后我们枚举母串与T近似匹配的位置,假设匹配完后缀长度为 l e n len len,前缀长度为 N N N,那我们可以从根节点开始走所有路径,走N次(不能走到叶子节点),定义走完N次后走到 u u u节点的次数为 d p [ N ] [ u ] dp[N][u] dp[N][u],那么 a n s + = d p [ N ] [ u ] ∗ d [ u ] ∗ 2 l e n ans += dp[N][u]*d[u]*2^{len} ans+=dp[N][u]d[u]2len d p dp dp的转移:找到 u u u的儿子 s o n son son(没有儿子沿着 f a i l fail fail指针去找儿子),如果 s o n son son不为叶子,那么 d p [ N + 1 ] [ s o n ] + = d p [ N ] [ u ] dp[N+1][son]+=dp[N][u] dp[N+1][son]+=dp[N][u]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 40 * 41;
int ch[maxn][2], f[maxn], sz, leaf[maxn], n;
ll d[maxn], P[41], dp[2][maxn];
char s[41];
void init() {
    for (int i = 0; i <= sz; i++) {
        f[i] = d[i] = leaf[i] = 0;
        memset(ch[i], 0, sizeof(ch[i]));
    }
    sz = 0;
}
void insert(char *S) {
    int o = 0;
    for (int i = 1; i <= n; i++) {
        int c = S[i] - '0';
        if (!ch[o][c])
            ch[o][c] = ++sz;
        o = ch[o][c];
    }
    leaf[o] = 1;
}
void build() {
    int o = 0;
    queue<int> q;
    for (int i = 0; i < 2; i++)
        if (ch[o][i])
            q.push(ch[o][i]);
    while (!q.empty()) {
        o = q.front();
        q.pop();
        for (int i = 0; i < 2; i++)
            if (ch[o][i]) {
                int v = ch[o][i];
                f[v] = ch[f[o]][i];
                q.push(v);
            }
            else
                ch[o][i] = ch[f[o]][i];
    }
}
int find(int o, int p) {
    if (leaf[o] && p != n + 1)
        return 0;
    if (p == n + 1)
        return 1;
    int c = s[p] - '0';
    return find(ch[o][c], p + 1);
}
void clear(int cur) {
    for (int i = 0; i <= sz; i++)
        dp[cur][i] = 0;
}
ll solve() {
    int m;
    scanf("%d%d%s", &n, &m, s + 1);
    init();
    insert(s);
    for (int i = 1; i <= n; i++) {
        s[i] = 1 ^ (s[i] - '0') + '0';
        insert(s);
        s[i] = 1 ^ (s[i] - '0') + '0';
    }
    build();
    d[0] = n + 1;
    for (int i = 1; i <= sz; i++) {
        d[i] += find(i, 1);
        for (int j = 1; j <= n; j++) {
            s[j] = 1 ^ (s[j] - '0') + '0';
            d[i] += find(i, 1);
            s[j] = 1 ^ (s[j] - '0') + '0';
        }
    }
    ll ans = d[0] * P[m - n];
    clear(1);
    dp[1][0] = 1;
    for (int i = 1, cur = 0; i <= m - n; i++, cur ^= 1) {
        clear(cur);
        for (int j = 0; j <= sz; j++)
            for (int k = 0; k < 2; k++) {
                int v = ch[j][k];
                if (leaf[v])
                    continue;
                ans += dp[!cur][j] * d[v] * P[m - n - i];
                dp[cur][v] += dp[!cur][j];
            }
    }
    return ans;
}
int main(){
    int T;
    P[0] = 1;
    for (int i = 1; i < 41; i++)
        P[i] = P[i - 1] * 2;
    scanf("%d", &T);
    while (T--)
        printf("%lld\n", solve());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值