• [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];
    		}
    	}
    }
    
    

  • 相关阅读:
    codevs2606 约数和问题
    UOJ150 运输计划
    codevs1279 Guard 的无聊
    codevs1997 守卫者的挑战
    codevs1291 火车线路
    codevs1217 借教室
    codevs1281 Xn数列
    codevs1218 疫情控制
    codevs1199 开车旅行
    BZOJ1941 [Sdoi2010]Hide and Seek
  • 原文地址:https://www.cnblogs.com/rvalue/p/10498477.html
Copyright © 2020-2023  润新知