Educational Codeforces Round 94 (Rated for Div. 2) F.x-prime Substrings(AC自动机(模板)+dp 禁止串 最少删除字符)

题目

给一个仅有数字1-9构成的串s(|s|<=1e3),

计f(l,r)是[l,r]的数位和,对于[l1,r1],计f(l1,r1)=x,

如果不存在其子区间l1<=l2<=r2<=r1,

使得f(l2,r2)不等于x,且f(l2,r2)整除x

则称[l1,r1]是x-prime的

现给定x<=20,求最少删除的s串的字符数,

使得删除后的串不存在x-prime子串,

注意删除了一个字符之后,前半段和后半段拼接在一起

思路来源

hcn代码

https://blog.csdn.net/Code92007/article/details/87315450

https://blog.csdn.net/Code92007/article/details/100657492

https://blog.csdn.net/Code92007/article/details/104231511

心得

其实是一个经典问题,自己之前也做过,CF稍微改改就不会了

个人感觉,是这么三个题的拼接,

一个用到了AC自动机ban串的套路,一个用到了删除字符的思想,一个就是被ban的串为1时dp怎么设计…

感谢一波hcn,让我发现我之前的AC自动机的板子是假的orz……

其实这个东西叫trie图优化,就是后继不存在的时候nex[x][i]=nex[fail[x]][i],

之前也用过,但一直不知道暴力回跳洛谷能卡……

不匹配的时候,就可以在这个DFA上一直走了,

AC自动机扩展和匹配的时候,也不用一直暴力fail回跳了,详见代码

题解

x<=20时,被ban的x-prime串貌似不会超过5e3个,暴力搞出来,插到trie树里,并建AC自动机

问题转化成,有m个被ban的串,一个母串s,最少删掉多少字符,使得一个被ban的串都不出现

dp[i][j]表示考虑s的前i个字母,当前在自动机j这个节点上,所需要删除的最小的字符

trie图上,nex[j][v]是j遇到v这个数字后转移到的字母,如果自动机上存在后继就走到后继,否则走到fail

注意到,节点j如果被ban不能转移,如果后继被ban不能转移,

否则当前在节点j上,遇到数字v,要么转移到nex[j][v],要么删掉这个字符保留在j

最终输出答案即可

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10,M=3e4+10,INF=0x3f3f3f3f,mod=1e9+7;
typedef long long ll;
char s[N];
int a[N],now[N],n,x,dp[N][M];//dp[i][j]表示匹配串第i 当前在自动机第j个节点的最少删的代价
namespace ac{
    int nex[M][10],fail[M],c;
    bool ban[M];
    void init(){
        c=0;
        memset(nex[c],0,sizeof nex[c]);
        ban[c]=fail[c]=0;
    }
    void add(int a[],int n){
        int rt=0;
        for(int i=0;i<n;++i){
            int x=a[i];
            if(!nex[rt][x]){
                nex[rt][x]=++c;
                memset(nex[c],0,sizeof nex[c]);
                ban[c]=fail[c]=0;
            }
            rt=nex[rt][x];
        }
        ban[rt]=1;
    }
    void bfs(){
        int rt=0;
        queue<int>q;
        for(int i=0;i<10;++i){
            int x=nex[rt][i];
            if(x){
                q.push(x);
                fail[x]=rt;
            }
        }
        while(!q.empty()){
            int x=q.front();q.pop();
            ban[x]|=ban[fail[x]];
            for(int j=0;j<10;++j){
                int ch=nex[x][j];
                if(ch){
                    q.push(ch);
                    fail[ch]=nex[fail[x]][j];
                }
                else{
                    nex[x][j]=nex[fail[x]][j];
                }
            }
        }
    }
};
using namespace ac;
void dfs(int i,int sum){
    if(sum==x){
        for(int j=1;j<i;++j){
            for(int k=j;k<i;++k){
                int tmp=now[k]-now[j-1];
                if(tmp!=x && x%tmp==0){
                    return;
                }
            }
        }
        add(a+1,i-1);
        return;
    }
    for(int j=1;j<10;++j){
        if(sum+j>x)break;
        a[i]=j;now[i]=sum+j;
        dfs(i+1,sum+j);
    }
}
void ckmin(int &x,int y){
    x=min(x,y);
}
int main(){
    scanf("%s%d",s,&x);
    init();
    dfs(1,0);
    bfs();
    memset(dp,INF,sizeof dp);
    dp[0][0]=0;
    n=strlen(s);
    for(int i=0;i<n;++i){
        int v=s[i]-'0';
        for(int j=0;j<=c;++j){
            int to=nex[j][v];
            if(ban[j] || ban[to])continue;
            ckmin(dp[i+1][to],dp[i][j]);
        }
        for(int j=0;j<=c;++j){
            if(ban[j])continue;
            ckmin(dp[i+1][j],dp[i][j]+1);
        }
    }
    int ans=INF;
    for(int j=0;j<=c;++j){
        if(ban[j])continue;
        ckmin(ans,dp[n][j]);
    }
    printf("%d\n",ans);
    return 0;
}

 

Bonus

同一天晚上牛客F题出了一个高仿的,那就也写一下吧

题目

牛妹有一本魔法字典书,这个书上有N(N<=200)个魔法字符串,

每个魔法字符串有一个魔法值val(−1000<=val<=1000)

现在牛妹要阅读一个长度为L(L<=1e3)的魔法密语,

你能构造出一个长度为L的密语,使得牛妹读完密语之后获得魔法值最多嘛?

牛牛只需输出最多的魔法值即可,魔法密语必须要求26个小写字母组成

数据保证Σ∣s∣<=2000,,并且s均为小写字母构成

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10,L=1e3+10,M=2e3+10,INF=0x3f3f3f3f;
typedef long long ll;
char s[N];
int n,l;
ll v,dp[L][N];
namespace ac{
    int nex[M][26],fail[M],c;
    ll w[M];
    void init(){
        c=0;
        memset(nex[c],0,sizeof nex[c]);
        w[c]=fail[c]=0;
    }
    void add(char s[],int n,ll v){
        int rt=0;
        for(int i=0;i<n;++i){
            int x=s[i]-'a';
            if(!nex[rt][x]){
                nex[rt][x]=++c;
                memset(nex[c],0,sizeof nex[c]);
                w[c]=fail[c]=0;
            }
            rt=nex[rt][x];
        }
        w[rt]+=v;
    }
    void bfs(){
        int rt=0;
        queue<int>q;
        for(int i=0;i<26;++i){
            int x=nex[rt][i];
            if(x){
                q.push(x);
                fail[x]=rt;
            }
        }
        while(!q.empty()){
            int x=q.front();q.pop();
            w[x]+=w[fail[x]];
            printf("x:%d w:%lld\n",x,w[x]);
            for(int j=0;j<26;++j){
                int ch=nex[x][j];
                if(ch){
                    q.push(ch);
                    fail[ch]=nex[fail[x]][j];
                }
                else{
                    nex[x][j]=nex[fail[x]][j];
                }
            }
        }
    }
};
using namespace ac;
void ckmax(ll &x,ll y){
    x=max(x,y);
}
int main(){
    scanf("%d%d",&n,&l);
    init();
    for(int i=1;i<=n;++i){
        scanf("%s%lld",s,&v);
        add(s,strlen(s),v);
    }
    bfs();
    memset(dp,128,sizeof dp);
    dp[0][0]=0;
    n=strlen(s);
    for(int i=0;i<l;++i){
        for(int v=0;v<26;++v){
            for(int j=0;j<=c;++j){
                int to=nex[j][v];
                ckmax(dp[i+1][to],dp[i][j]+w[to]);
            }
        }
    }
    ll ans=-INF;
    for(int j=0;j<=c;++j){
        ckmax(ans,dp[l][j]);
    }
    printf("%lld\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Code92007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值