题目
给一个仅有数字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;
}