BZOJ4560: [JLoi2016]字符串覆盖【KMP+贪心+DP】

题目描述:

字符串A有N个子串B1,B2,…,Bn。如果将这n个子串分别放在恰好一个它在A中出现的位置上(子串之间可以重叠),这样A中的若干字符就被这N个子串覆盖了。问A中能被覆盖字符个数的最小值和最大值。
字符串长度A<=10000,N<=4,子串长度<=1000

题目分析:

首先用KMP求出每个串在A中的出现位置。

先考虑直接DP的做法:
f [ S ] [ i ] f[S][i] f[S][i]表示已经放的串的状态为 S S S,最后一个串的右端点为 i i i的最小覆盖长度。

  • 最后一个串不与其它串相交,枚举最后一个串 k k k f [ S ] [ i ] = L [ k ] + min ⁡ 0 ≤ j ≤ i − L [ k ] f [ S   x o r   k ] [ j ] f[S][i]=L[k]+\min_{0\le j\le i-L[k]} f[S~xor~k][j] f[S][i]=L[k]+0jiL[k]minf[S xor k][j]
  • 最后一个串与某个串相交,枚举最后一个串 k k k f [ S ] [ i ] = i + min ⁡ i − L [ k ] < j < i f [ S   x o r   k ] [ j ] − j f[S][i]=i+\min_{i-L[k]<j<i}f[S~xor~k][j]-j f[S][i]=i+iL[k]<j<iminf[S xor k][j]j
    需要注意这里,求最小值时需要将被包含的串去掉,否则上面这个转移就会少加。

这个做法同样适用于求最大值,第一个转移用前缀优化,第二个转移用树状数组优化即可,但是这样做复杂度略高,详见这里

当求最小值时,经过观察可以发现上面两个转移当 j j j不在对应的范围时得到的答案一定不会更优,所以可以直接将范围去掉,改为用两个数组 g [ S ] , h [ S ] g[S],h[S] g[S],h[S]分别记录 f [ S ] [ i ] f[S][i] f[S][i] f [ S ] [ i ] − i f[S][i]-i f[S][i]i的最小值。转移就变成了 O ( 1 ) O(1) O(1)。复杂度 O ( 2 n ∗ n ∗ ∣ A ∣ ) O(2^n*n*|A|) O(2nnA)

当求最大值时范围是必要的,于是不用DP,改用贪心的做法:先枚举四个串左端点的顺序,再枚举第一个串的位置,后面的串的位置应当是上一个串内的最后一个匹配位置或是上一个串后的第一个匹配位置。复杂度 O ( n ! ∗ 2 n ∗ n l o g A ) O(n!*2^n*nlogA) O(n!2nnlogA)

PS:对于求最小值还有递归DP做法,详见:题解 P3269 【[JLOI2016]字符串覆盖】
PS2:因为 n ≤ 4 n\le4 n4,所以还有直接贪心的做法:在这里插入图片描述

Code:

#include<bits/stdc++.h>
#define maxn 10005
using namespace std;
const int inf = 0x3f3f3f3f;
int T,K,n,L[4],X[4][maxn],Y[4];
char A[maxn],B[4][maxn];
bool ok[4][maxn];
void KMP(char *s,char *t,bool *f,int *pos,int &cnt,int m){
	static int fa[maxn]={-1};
	for(int i=0,j=-1;i<m;fa[++i]=++j)
		while(j!=-1&&t[i]!=t[j]) j=fa[j];
	for(int i=0,j=0;i<=n;i++,j++){
		if(j==m) f[pos[++cnt]=i-m]=1,j=fa[j];
		if(i==n) break;
		while(j!=-1&&s[i]!=t[j]) j=fa[j];
	}
}
bool vis[4]; int ans;
void dfs(int k,int R,int s){
	if(k==K) {ans=max(ans,s);return;}
	for(int i=0,x;i<K;i++) if(!vis[i]){
		vis[i]=1;
		if(x=upper_bound(X[i]+1,X[i]+1+Y[i],R)-X[i]-1)
			dfs(k+1,max(R,X[i][x]+L[i]-1),s+max(0,X[i][x]+L[i]-1-R));
		if(++x<=Y[i])
			dfs(k+1,X[i][x]+L[i]-1,s+L[i]);
		vis[i]=0;
	}
}
int solve_max(){
	ans=0;
	for(int i=0;i<K;i++) vis[i]=1,dfs(1,X[i][1]+L[i]-1,L[i]),vis[i]=0;
	return ans;
}
int f[1<<4],g[1<<4],h[1<<4];
inline bool check(int i,int j){//whether i is substring of j
	int k=lower_bound(X[i]+1,X[i]+1+Y[i],X[j][1])-X[i];
	if(k<=Y[i]&&X[i][k]+L[i]<=X[j][1]+L[j]) return 1;
	return 0;
}
int solve_min(){
	memset(g,0x3f,sizeof g),g[0]=0,memset(h,0x3f,sizeof h);
	int S0=0;
	for(int i=0;i<K;i++) for(int j=i+1;j<K;j++) if(check(i,j)) S0|=1<<i; 
	for(int i=0;i<n;i++){
		memset(f,0x3f,sizeof f);
		for(int s=1;s<1<<K;s++) if(!(s&S0)){
			for(int j=0;j<K;j++) if((s>>j&1)&&i+1>=L[j]&&ok[j][i-L[j]+1])
				f[s]=min(f[s],min(g[s^(1<<j)]+L[j],h[s^(1<<j)]+i));
			g[s]=min(g[s],f[s]),h[s]=min(h[s],f[s]-i);
		}
	}
	return g[((1<<K)-1)^S0];
}
int main()
{
	scanf("%d",&T);
	while(T--){
		memset(ok,0,sizeof ok),memset(Y,0,sizeof Y);
		scanf("%s%d",A,&K),n=strlen(A);
		for(int i=0;i<K;i++) scanf("%s",B[i]),L[i]=strlen(B[i]);
		for(int i=0;i<K;i++) for(int j=i+1;j<K;j++) if(L[i]>L[j]) swap(L[i],L[j]),swap(B[i],B[j]);
		for(int i=0;i<K;i++) KMP(A,B[i],ok[i],X[i],Y[i],L[i]);
		printf("%d %d\n",solve_min(),solve_max());
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值