洛谷P3269 [JLOI2016] 字符串覆盖

题目描述

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

输入格式

第一行包含一个正整数T,表示数据组数。保证T<=10。

接下来依次描述T组数据,每组数据中:

第一行包含一个由小写字母组成的字符串,表示母串A。

第二行包含一个整数N,表示子串的个数。

接下来N行,每行包含一个由小写字母组成的字符串,描述子串。数据保证所有子串均在母串中出现。

输出格式

输出为T行,对应每组数据的答案。每行包含两个整数Minans和Maxans,分别表示对应数据中能被覆盖字符数量的最小值和最大值。

输入输出样例

输入 #1

2
hello
4
he
l
l
o
abacaba
4
ab
ba
a
c

输出 #1

4 5 
4 6

说明/提示

字符串长度A<=10000,N<=4,子串长度<=10000

code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
	rg T data=0;
	rg int w=1;
	rg char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		data=data*10+ch-'0';
		ch=getchar();
	}
	return data*w;
}
template<class T>il T read(rg T&x)
{
	return x=read<T>();
}
typedef long long ll;
using namespace std;
 
co int N=1e4+1;
char s[N],t[5][N];
int n,m,len[5],cnt[5];
int fail[N];
struct data
{
	int l,r,id;
	
	bool operator<(co data&x)co
	{
		return l<x.l;
	}
}b[5][N],c[4*N];
 
namespace MAX
{
	bool flag[5];
	int ans;
	
	void dfs(int num,int pl,int pr,int sum)
	{
		if(num==m)
		{
			ans=std::max(ans,sum);
			return;
		}
		for(int i=1;i<=m;++i)if(!flag[i])
		{
			int u=0,v=0;
			for(int j=1;j<=cnt[i];++j)if(b[i][j].l>=pl)
			{
				if(b[i][j].l<=pr) u=j;
				else {v=j;break;}
			}
			flag[i]=1;
			if(u)
				dfs(num+1,b[i][u].l,std::max(pr,b[i][u].r),sum+std::max(b[i][u].r-pr,0));
			if(v)
				dfs(num+1,b[i][v].l,std::max(pr,b[i][v].r),sum+b[i][v].r-b[i][v].l+1);
			flag[i]=0;
		}
	}
	
	int solve()
	{
//		cerr<<"solve max"<<endl;
		ans=0;
		dfs(0,0,0,0);
		return ans;
	}
}
 
namespace MIN
{
	int min[N*4][1<<4],f[N*4][1<<4],tree[N<<4][1<<4];
	
	void rebuild()
	{
		bool flag[5]={0};
		for(int i=1;i<=m;++i)
			for(int j=1;j<=m;++j)
				if(i!=j&&len[i]<len[j]||len[i]==len[j]&&i>j)
		for(int k=1;k<=cnt[i];++k)
			if(b[i][k].l>=b[j][1].l&&b[i][k].r<=b[j][1].r) {flag[i]=1;break;}
		n=0;int m2=0;
		for(int i=1;i<=m;++i)if(!flag[i])
		{
			for(int j=1;j<=cnt[i];++j)
				c[++n]=b[i][j],c[n].id=m2;
			++m2;
		}
		m=m2;
		sort(c+1,c+n+1);
	}
	
	void insert(int x,int s,int l,int r,int p,int v)
	{
		tree[x][s]=std::min(tree[x][s],v);
		if(l==r)
			return;
		int m=(l+r)/2;
		if(p<=m)
			insert(x<<1,s,l,m,p,v);
		else
			insert(x<<1|1,s,m+1,r,p,v);
	}
	
	int query(int x,int s,int l,int r,int ql,int qr)
	{
		if(ql>qr)
			return N;
		if(ql<=l&&r<=qr)
			return tree[x][s];
		int m=(l+r)/2;
		if(qr<=m)
			return query(x<<1,s,l,m,ql,qr);
		if(ql>=m+1)
			return query(x<<1|1,s,m+1,r,ql,qr);
		return std::min(query(x<<1,s,l,m,ql,qr),query(x<<1|1,s,m+1,r,ql,qr));
	}
	
	int solve()
	{
//		cerr<<"solve min"<<endl;
		rebuild();
		memset(f,0x3f,sizeof f);f[0][0]=0;
		memset(min,0x3f,sizeof min);min[0][0]=0;
		memset(tree,0x3f,sizeof tree);
		int p=0;
		for(int i=1;i<=n;++i)
		{
			while(c[p+1].r<c[i].l) ++p;
			for(int j=0;j<(1<<m);++j)if(j&(1<<c[i].id))
				f[i][j]=std::min(min[p][j^(1<<c[i].id)]+c[i].r-c[i].l+1,query(1,j^(1<<c[i].id),1,n,p+1,i-1)+c[i].r);
			for(int j=0;j<(1<<m);++j)
			{
				min[i][j]=std::min(min[i-1][j],f[i][j]);
				insert(1,j,1,n,i,f[i][j]-c[i].r);
			}
		}
		return min[n][(1<<m)-1];
	}
}
 
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int kase;
	read(kase);
	while(kase--)
	{
		scanf("%s",s+1);
		n=strlen(s+1);
		read(m);
		for(int i=1;i<=m;++i)
		{
			scanf("%s",t[i]+1);
			len[i]=strlen(t[i]+1),cnt[i]=0;
			for(int j=1;j<len[i];++j)
			{
				int k=fail[j];
				while(k&&t[i][k+1]!=t[i][j+1])
					k=fail[k];
				fail[j+1]=t[i][k+1]==t[i][j+1]?k+1:0;
			}
			int j=0;
			for(int k=1;k<=n;++k)
			{
				while(j&&t[i][j+1]!=s[k])
					j=fail[j];
				if(t[i][j+1]==s[k])
					++j;
				if(j==len[i])
					++cnt[i],b[i][cnt[i]].l=k-len[i]+1,b[i][cnt[i]].r=k;
			}
			sort(b[i]+1,b[i]+cnt[i]+1);
//			for(int j=1;j<=cnt[i];++j)
//				cerr<<j<<" l="<<b[i][j].l<<" r="<<b[i][j].r<<endl;
		}
		printf("%d %d\n",MIN::solve(),MAX::solve());
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值