【Codeforces 1163D】Mysterious Code | AC自动机、fail树上dp

题目链接:https://codeforces.com/contest/1163/problem/D

题目大意:

首先定义f(s,t)函数,表示t中含有s串个数

之后首先给出一个c串,由'*'和小写字母组成,其中'*'可以替换为26个字母中的任意一个

之后给出两个串s,t。

要求你根据c串中的'*'构造c串使得f(s,c) - f(t,c)最大

题目思路:

多字符串包含关系必然会想到AC自动机

首先考虑,建立AC自动机时,节点得权值情况

在字典树上s的终结点权值为1,t的终结点权值为-1

之后每个节点的权值呢?

就是fail树的前缀和,理解很容易当前串会包含fail树的前缀,只要包含这个前缀就会产生贡献

之后进行dp就可以了

由于s和t的长度都很小,所以AC自动机的节点也超不过100个

所以我们可以想到n*node*26的dp方案

首先枚举第i个字母,前驱是第k个节点,当前节点选择j

具体转移在注释:

        for(int i=1;i<=len;i++){
            for(int k=0;k<nNode;k++){///上一个节点是k
                if(dp[i-1][k] == -INF) continue;
                if(p[i] == '*'){
                    for(int j=0;j<26;j++){///当前匹配字符
                        int temp = k;
                        while(~temp&&t[temp][j] == 0) temp = fail[temp];///找到可以有贡献的第一个节点
                        if(temp == -1) dp[i][0] = max(dp[i][0],dp[i-1][k]);
                        else{
                            int op = t[temp][j];///虽然找到了前缀匹配但是还是需要从dp[i-1][k]转移过来
                            dp[i][op] = max(dp[i][op],dp[i-1][k]+val[op]+val[fail[op]]);///由于此时只有两个串直接算即可
                        }
                    }
                }
                else{///只能单独选择
                        int temp = k;
                        while(~temp&&t[temp][p[i]-'a'] == 0) temp = fail[temp];
                        if(temp == -1) dp[i][0] = max(dp[i][0],dp[i-1][k]);
                        else{
                            int op = t[temp][p[i]-'a'];
                            dp[i][op] = max(dp[i][op],dp[i-1][k]+val[op]+val[fail[op]]);
                        }
                }
            }
        }

变式:

理解上述问题之后,这个问题还可以做一个变式,首先给出n个字符串,并且给出每个字符串的出现一次的权值,让你构造一个长度为m的字符串,使得这个字符串的权值最小,唯一不同就是需要预处理一下匹配到j节点时会产生的权值

Code:

/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pp;
const ll INF=1e17;
const int Maxn=2e7+10;
const int maxn =1e3+10;
const int mod=1e9+9;
const int Mod = 1e9+7;
///const double eps=1e-10;
inline bool read(ll &num)
{char in;bool IsN=false;
    in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
struct Aho{
    ll nNode=0;
    int t[maxn][26];///tire树
    int val[maxn];
    int fail[maxn];///fail指针
    int id[maxn];
    ll dp[maxn][105];///当前第i个字符匹配到j节点产生的最大值
    void _inint_(){nNode = 1;}
    int newNode(){
        for(int i=0; i<26; ++i) t[nNode][i]=0;
        val[nNode]=0;
        return nNode++;
    }
    void Insert(char *p,int f){///tire的插入
        int len=strlen(p);
        int rt=0;
        for(int i=0;i<len;i++){
            int x=p[i]-'a';
            if(!t[rt][x]) t[rt][x]=newNode();
            rt=t[rt][x];
        }
        val[rt] = f;
    }
    void build(){///fail树的建立
        queue<int>q;
        fail[0]=-1;///初始点设为-1 无fail
        q.push(0);
        int cnt = 0;
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(t[u][i]){
                    id[++cnt] = t[u][i];
                    if(u==0) fail[t[u][i]]=0;
                    else{
                        int v=fail[u];
                        while(~v){
                            if(t[v][i]){
                                fail[t[u][i]]=t[v][i];
                                break;
                            }
                            v=fail[v];
                        }
                        if(v==-1) fail[t[u][i]]=0;
                    }
                    q.push(t[u][i]);
                }
            }
        }
    }
    ll Match(char *p){
        int len = strlen(p+1);
        for(int i=0;i<=len;i++)
            for(int k=0;k<nNode;k++)
                dp[i][k]=-INF;
        dp[0][0] = 0;
        for(int i=1;i<=len;i++){
            for(int k=0;k<nNode;k++){///上一个节点是k
                if(dp[i-1][k] == -INF) continue;
                if(p[i] == '*'){
                    for(int j=0;j<26;j++){///当前匹配字符
                        int temp = k;
                        while(~temp&&t[temp][j] == 0) temp = fail[temp];///找到可以有贡献的第一个节点
                        if(temp == -1) dp[i][0] = max(dp[i][0],dp[i-1][k]);
                        else{
                            int op = t[temp][j];///虽然找到了前缀匹配但是还是需要从dp[i-1][k]转移过来
                            dp[i][op] = max(dp[i][op],dp[i-1][k]+val[op]+val[fail[op]]);///由于此时只有两个串直接算即可
                        }
                    }
                }
                else{///只能单独选择
                        int temp = k;
                        while(~temp&&t[temp][p[i]-'a'] == 0) temp = fail[temp];
                        if(temp == -1) dp[i][0] = max(dp[i][0],dp[i-1][k]);
                        else{
                            int op = t[temp][p[i]-'a'];
                            dp[i][op] = max(dp[i][op],dp[i-1][k]+val[op]+val[fail[op]]);
                        }
                }
            }
        }
        ll maxl = -INF;
        for(int k=0;k<nNode;k++) maxl = max(maxl,dp[len][k]);
        return maxl;
    }
}aho;
char s[maxn],t[maxn];
int main()
{
    scanf("%s",t+1);
    aho._inint_();
    scanf("%s",s);aho.Insert(s,1);
    scanf("%s",s);aho.Insert(s,-1);
    aho.build();
    printf("%lld\n",aho.Match(t));
    return 0;
}
/**
****ab****
abcd
cdab
**/

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/107592300
今日推荐