• BZOJ2806 [Ctsc2012]Cheat 【后缀自动机 + 二分 + 单调队列优化DP】


    题目


    输入格式

    第一行两个整数N,M表示待检查的作文数量,和小强的标准作文库
    的行数
    接下来M行的01串,表示标准作文库
    接下来N行的01串,表示N篇作文

    输出格式

    N行,每行一个整数,表示这篇作文的Lo 值。

    输入样例

    1 2

    10110

    000001110

    1011001100

    输出样例

    4

    提示

    输入文件不超过1100000字节

    注意:题目有改动,可识别的长度不小于90%即可,而不是大于90%

    题解

    想来练练SAM,却跪在了单调队列DP上。。。QAQ

    根据后缀数组进行多串匹配时,用一个未出现的字符将各串连接起来形成一个串
    我们可以照搬到后缀自动机来,对m个模板串建立后缀自动机

    此时我们可以在后缀自动机上走一遍求出匹配串每个位置为结尾所能匹配的最大长度【见代码init】
    记为len[i],即表示匹配的子串([i - len[i] + 1,i])可以与模板串匹配

    有了这些,我们如何计算(L_0)
    仔细思考发现L具有单调性,即在长度至少为L时选择的最小切割方案下,对长度小于L的限制仍然满足,长度小于L的可以照搬L的切割方法而满足条件

    这个时候我们就可以二分了
    对于长度L,我们可以用DP检验,令f[i]表示前i个字符在L限制下能匹配的最大长度
    首先f[i]至少等于f[i - 1],因为i可以单独被化为出来而转移到f[i - 1]【也就是说f[i]单调不下降,这个待会会用到】
    我们有状态转移方程:(f[i] = max{f[j] + i - j},jin [i - len[i],i - L])

    解释:由于L的限制,要切割出以i结尾产生贡献的区间,必须在i - L及之前,而i最多匹配到i - len[i] + 1,所以(jin [i - len[i],i - L])
    最后的匹配长度就是i割出这段的长度 + f[j]

    这样做是(O(n^2logn))的,不能满足题意
    考虑优化
    我们将方程变形(f[i] - i = f[j] - j),左边是只与i有关的量,与决策无关
    右边是只与j有关的量,由f[j]的定义,f[j] - j表示前j个没匹配的个数【负数】,由于没匹配的个数不会变少,所以f[j] - j单调递减

    也就是说决策具有单调性,且只与j有关,可以采用单调队列优化
    【一定要好好写队列QAQ,WA了几发】

    呼啦啦~~~搞完了【CTSC的题目哇。】

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    #define REP(i,n) for (int i = 1; i <= (n); i++)
    #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
    #define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
    using namespace std;
    const int maxn = 3000005,maxm = 100005,INF = 1000000000;
    int pre[maxn],ch[maxn][3],step[maxn],cnt,last;
    char s[maxn];
    void ins(int x){
    	int p = last,np = ++cnt;
    	last = np; step[np] = step[p] + 1;
    	while (p && !ch[p][x]) ch[p][x] = np,p = pre[p];
    	if (!p) pre[np] = 1;
    	else {
    		int q = ch[p][x];
    		if (step[q] == step[p] + 1) pre[np] = q;
    		else {
    			int nq = ++cnt; step[nq] = step[p] + 1;
    			memcpy(ch[nq],ch[q],sizeof(ch[q]));
    			pre[nq] = pre[q]; pre[q] = pre[np] = nq;
    			while (ch[p][x] == q) ch[p][x] = nq,p = pre[p];
    		}
    	}
    }
    int q[maxn],head,tail,f[maxn],N,len[maxn];
    bool check(int L){
    	head = tail = f[0] = 0;
    	for (int i = 1; i <= N; i++){
    		f[i] = f[i - 1];
    		int l = i - len[i],r = i - L;
    		if (r >= 0){
    			while (head < tail && f[q[tail - 1]] - q[tail - 1] < f[r] - r) tail--;
    			q[tail++] = r;
    		}
    		while (head < tail && q[head] < l) head++;
    		if (head < tail) f[i] = max(f[i],f[q[head]] - q[head] + i);
    	}
    	return 10 * f[N] >= 9 * N;
    }
    void init(){
    	int u = 1,id,ans = 0;
    	for (int i = 1; i <= N; i++){
    		id = s[i] - '0';
    		if (ch[u][id]) len[i] = ++ans,u = ch[u][id];
    		else {
    			while (u != 1 && !ch[u][id]) u = pre[u];
    			if (u == 1) len[i] = ans = 0;
    			else len[i] = ans = step[u] + 1,u = ch[u][id];
    		}
    	}
    }
    int main(){
    	int n,m; last = cnt = 1;
    	scanf("%d%d",&n,&m);
    	while (m--){
    		scanf("%s",s); N = strlen(s);
    		for (int i = 0; i < N; i++) ins(s[i] - '0');
    		ins(2);
    	}
    	while (n--){
    		scanf("%s",s + 1); N = strlen(s + 1);
    		init();
    		int l = 0,r = N,mid,ans = 0;
    		while (l <= r){
    			mid = l + r >> 1;
    			if (check(mid)) l = mid + 1,ans = mid;
    			else r = mid - 1;
    		}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    在ThinkPHP中生成中文验证码
    Touch event in certain color rect
    安装GD后不支持PNG或JPG的修复办法
    iPhone开发:proximityMonitoring邻近检测
    开放CSDN博客-欢迎到访-另附声明
    (实例篇)LNMP 1.4一键安装包,安装教程
    流量相关说明
    一个空间主机安装多个网站的方法
    怎么使用linux命令重启服务器
    CentOS、Ubuntu、Debian三个linux比较异同
  • 原文地址:https://www.cnblogs.com/Mychael/p/8309918.html
Copyright © 2020-2023  润新知