Codeforces1400 F. x-prime Substrings(AC自动机上dp)

题意:

给定只有[1,9]组成的数字串s,和一个整数x。
定义f(l,r)为数字串s[l,r]的数位和,
称s[l,r]是x-prime的,当且仅当:
1.f(l,r)=x
2.不存在两个值l2,r2,满足l<=l2<=r2<=r,且f(l2,r2)!=x,同时x能够被f(l2,r2)整除。

一次操作你可以删掉串中的一个字符,
问最少进行多少次操作,能使得串s不存在x-prime的的子串。

数据范围:|s|<=1e3,x<=20

解法:
因为x很小,每个x-prime串的长度不会超过x,
x-prime数量不会很多,可以dfs出所有x-prime串,
然后问题变为删除最少的字符,使得s串中不包含任何x-prime串.

对所有x-prime串建立ac自动机,
那么问题变为在ac自动机上按照s串走,不能走到x-prime的结尾处,
令d[i][j]表示走i步,当前在节点j位置,满足条件的最小操作次数,
对于d[i][j]:
如果要删掉s[i+1]那么d[i+1][j]=d[i][j]+1,
如果不删则d[i+1][s[i+1]],d[i+1][s[i+1]]=d[i][j]
在ac自动机上dp一下即可.
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e4+5;
char s[maxm];
char t[maxm];
int sum[maxm];
int a[maxm];
int n,x;
struct AC{
    int a[maxm][10],fail[maxm],val[maxm],tot=0;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(a[i],0,sizeof a[i]);
            val[i]=fail[i]=0;
        }
        tot=0;
    }
    void add(char *s){
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'0';
            if(!a[node][v])a[node][v]=++tot;
            node=a[node][v];
        }
        val[node]=1;
    }
    void build(){//构造fail
    	queue<int>q;
        for(int i=0;i<10;i++){
            if(a[0][i]){
                q.push(a[0][i]);
            }
        }
        while(!q.empty()){
            int x=q.front();
            q.pop();
            val[x]|=val[fail[x]];
            for(int i=0;i<10;i++){
                if(a[x][i]){
                    fail[a[x][i]]=a[fail[x]][i];
                    q.push(a[x][i]);
                }else{
                    a[x][i]=a[fail[x]][i];
                }
            }
        }
    }
    //
    int d[1005][maxm];
    int solve(){
        for(int i=0;i<=n;i++){
            for(int j=0;j<=tot;j++){
                d[i][j]=1e9;
            }
        }
        d[0][0]=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<=tot;j++){
                if(d[i][j]==1e9)continue;
                int v=s[i+1]-'0';
                int x=a[j][v];
                d[i+1][j]=min(d[i+1][j],d[i][j]+1);//删
                if(!val[x]){//不删
                    d[i+1][x]=min(d[i+1][x],d[i][j]);
                }
            }
        }
        int ans=1e9;
        for(int j=0;j<=tot;j++){
            ans=min(ans,d[n][j]);
        }
        return ans;
    }
}ac;
void dfs(int cur,int tot){
    if(tot>x)return ;
    if(tot==x){
        for(int i=1;i<cur;i++){
            sum[i]=sum[i-1]+a[i];
        }
        int ok=1;
        for(int i=1;i<cur&&ok;i++){
            for(int j=i;j<cur&&ok;j++){
                if(sum[j]-sum[i-1]!=x&&x%(sum[j]-sum[i-1])==0){
                    ok=0;
                }
            }
        }
        if(ok){
            int cnt=0;
            for(int i=1;i<cur;i++){
                t[cnt++]=a[i]+'0';
            }
            t[cnt]='\0';
            ac.add(t);
        }
        return ;
    }
    for(int i=1;i<=9;i++){
        a[cur]=i;
        dfs(cur+1,tot+i);
    }
}
signed main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    scanf("%d",&x);
    dfs(1,0);
    ac.build();
    int ans=ac.solve();
    cout<<ans<<endl;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值