Approximate Matching HihoCoder - 1877 ac自动机优化DP 套路

点我看题

题意:
给出n,m
给出一个长度为n的01串 S,
问你能构造出多少个01串, 这些01串的某一个字串与S至多只有一个位置不同。
题解:
首先想到dp[i][j][0/1] 表示 构造了前i个字符,与S的前j个字符 有0/1 字符不相同的方案数。
显然 ,无法很方便的转移,因为只要有一个字串满足条件就行了。

然后,有一个ac自动机很套路的用法.
这题跟hdu2858有些类似。
点我看题 最后面
设dp[i][j]表示构造前i个字符,最终状态到达i的不合法状态数。
因为最多只能有1个字符不同,那最多只有n+1个不同的串,把这n+1个串全部加入ac自动机当中。
除结尾的点外其他的点都为不合法点。
这样的话就很好转移了。
很套路的一道题。
PS。 北京五题有金,感觉很真实。

#include<bits/stdc++.h>
using namespace std;
const int SIGMA_SIZE = 2;
const int MAXNODE = 2500;
const int MAXS = 1005;
char str[1005];
long long dp[50][2000];
struct AC{
    int ch[MAXNODE][SIGMA_SIZE];
    int f[MAXNODE];
    int val[MAXNODE];
    int last[MAXNODE];
    int sz;
    void init(){
        sz = 1;
        memset(ch[0],0,sizeof (ch[0]));
    }
    inline int idx(char c){
        return c-'0';
    }
    void insert(char *s,int ids){
        int u =0 ,n= strlen(s);
        for(int i=0;i<n;i++){
            int c= idx(s[i]);
            if(!ch[u][c]){
                memset(ch[sz],0,sizeof ch[sz]);
                val[sz]=0;
                ch[u][c]= sz++;
            }
            u = ch[u][c];
        }
        val[u]=ids;
    }
    void getfail(){
        queue<int> Q;
        f[0]=0;
        for(int c= 0;c<SIGMA_SIZE;c++){
            int u=ch[0][c];
            if(u){
                f[u]=0;
                Q.push(u);
                last[u]=0;
            }
        }
        while(!Q.empty()){
            int r= Q.front();
            Q.pop();
            for(int c = 0;c<SIGMA_SIZE;c++){
                int u = ch[r][c];
                if(!u){
                    ch[r][c]=ch[f[r]][c];
                    continue;
                }
                Q.push(u);
                int v = f[r];
                while(v && !ch[v][c]) v= f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]]?f[u]:last[f[u]];
            }
        }
    }
} ac;

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ac.init();
        long long n,m;
        scanf("%lld %lld",&n,&m);
        scanf("%s",str);
        ac.insert(str,1);
        for(int i=0;i<n;i++){
            if(str[i]=='0'){
                str[i]='1';
                ac.insert(str,1);
                str[i]='0';
            }else{
                str[i]='0';
                ac.insert(str,1);
                str[i]='1';
            }
        }
        ac.getfail();
        for(int i=0;i<=m;i++){
            for(int j=0;j<=ac.sz;j++){
                dp[i][j]=0;
            }
        }
        dp[0][0]=1;
        for(int i=0;i<m;i++){
            for(int j=0;j<ac.sz;j++){
                if(!dp[i][j]) continue;
                for(int k=0;k<=1;k++){
                    int v=ac.ch[j][k];
                    if(ac.val[v])continue;
                    dp[i+1][v]+=dp[i][j];
                }
            }
        }
        long long sum = 0;
        for(int i=0;i<ac.sz;i++){
            sum+=dp[m][i];
        }
        printf("%lld\n",((1ll<<m)-sum));
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值