[BZOJ 2806][Ctsc2012]Cheat

[BZOJ 2806][Ctsc2012]Cheat

题意

给定一个 (m) 个串的字典, 对于 (n) 组查询串, 求一个最大的 (L_0) 使查询串能被拆分为若干子串使得其中是字典串的子串且长度不小于 (L_0) 的子串的长度之和不小于总长的 (90\%).

所有串均为01串, 输入文件大小不超过 (110000 exttt{B}).

题解

我可能在做这题前对SAM的理解有点偏差

首先不难看出这个 (L_0) 是可以二分的.

若查询串的一个子串同时也是字典中某个串的子串, 我们称之为有效子串.

然后我们尝试DP求出最优情况下的有效子串总长. 设 (dp_i) 表示长度为 (i) 的前缀中的有效子串总长的最大值. 我们可以枚举最后一个有效串的长度进行转移, 同时这一步还可以不选择任何子串 (即字符 (i) 不包含在任何有效子串中, 不做任何贡献地从 (dp_{i-1}) 转移). 于是我们需要快速知道以 (i) 为右端点的最长有效子串长度.

显然这一步我们可以对字典串建立广义SAM求出. 具体做法是把查询串丢到广义SAM上运行, 运行时如果能匹配就按匹配边走并产生 (+1) 贡献, 否则跳 (prt) 并将当前匹配长度更新为 (prt)(len/max) 长.

但是这样枚举长度转移复杂度可能是 (O(|S|^2)) 的.

容易发现每次 (i) 增加 (1) 后只会多一个决策点 (i-L_0). 且若决策集合中存在 (j<i) 满足 (dp_j + (i-j) le dp_i) , 则 (dp_j) 必定不优秀, 单调队列一下就可以做到 (O(n)) 了.

代码实现

#include <bits/stdc++.h>

const int MAXN=2e6+10;

int n;
int m;
int cnt=1;
int last=1;
int root=1;
int dp[MAXN];
int prt[MAXN];
int len[MAXN];
char buf[MAXN];
int chd[MAXN][2];

void Extend(int);
bool Check(int,int);

int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++){
		last=root;
		scanf("%s",buf);
		for(char* p=buf;*p!='';p++)
			Extend(*p-'0');
	}
	for(int i=0;i<n;i++){
		scanf("%s",buf+1);
		int len=strlen(buf+1);
		int l=0,r=len+1;
		while(r-l>1){
			int mid=(l+r)>>1;
			if(Check(mid,len))
				l=mid;
			else
				r=mid;
		}
		printf("%d
",l);
	}
	return 0;
}

bool Check(int L,int n){
//	printf("L=%d,n=%d
",L,n);
	int clen=0;
	int cur=root;
	std::deque<int> q;
	for(int i=1;i<=n;i++){
		dp[i]=dp[i-1];
		int x=buf[i]-'0';
		while(cur!=root&&!chd[cur][x]){
			cur=prt[cur];
			clen=len[cur];
		}
		if(chd[cur][x]){
			++clen;
			cur=chd[cur][x];
		}
		while(!q.empty()&&dp[q.back()]-q.back()<=dp[i-L]-(i-L))
			q.pop_back();
		q.push_back(i-L);
		while(!q.empty()&&q.front()<i-clen)
			q.pop_front();
		if(!q.empty())
			dp[i]=std::max(dp[i],dp[q.front()]+i-q.front());
	}
	return dp[n]*10>=n*9;
}

void Extend(int x){
	int p=last;
	int np=++cnt;
	len[last=np]=len[p]+1;
	while(p&&chd[p][x]==0)
		chd[p][x]=np,p=prt[p];
	if(!p)
		prt[np]=root;
	else{
		int q=chd[p][x];
		if(len[q]==len[p]+1)
			prt[np]=q;
		else{
			int nq=++cnt;
			memcpy(chd[nq],chd[q],sizeof(chd[q]));
			prt[nq]=prt[q];
			prt[q]=nq;
			prt[np]=nq;
			len[nq]=len[p]+1;
			while(p&&chd[p][x]==q)
				chd[p][x]=nq,p=prt[p];
		}
	}
}

原文地址:https://www.cnblogs.com/rvalue/p/10498477.html