zoukankan      html  css  js  c++  java
  • [BZOJ4032][HEOI2015]最短不公共子串(后缀自动机+序列自动机+DP)

    [BZOJ4032][HEOI2015]最短不公共子串(后缀自动机+序列自动机+DP)

    题面

    给两个小写字母串A,B,请你计算:
    (1) A的一个最短的子串,它不是B的子串
    (2) A的一个最短的子串,它不是B的子序列
    (3) A的一个最短的子序列,它不是B的子串
    (4) A的一个最短的子序列,它不是B的子序列

    分析

    先考虑(1)(3),对B建SAM

    对于询问(1).直接枚举子串左端点。对于每个左端点,向右扫的同时在SAM上匹配,当第一次失配的时候更新答案并break。

    对于询问(3).设(f_{i,j})为SAM节点(j)在A的前(i)位中匹配到的最长子序列长度。
    那么显然有(f_{i,delta(j,A_i)}=max(f_{i-1,j}+1)(delta(j,A_i) eq ext{NULL})),其中(delta)为自动机的转移函数。
    最终答案为(f_{n,q_0}),其中(q_0)为SAM的起始状态,代表空串


    考虑(2)(4)如何做。如果我们能构造出一个自动机,它能把询问串在某个串的所有子序列中匹配,那么就可以套用(1)(3)的方法。那么它的转移函数是根据子序列的,即(delta(x,c))表示(x)代表串后加一个字符(c)能匹配到的子序列。

    那么就可以用到序列自动机。其实很多人都在不知不觉中用过这个方法。其实很简单,(delta(i,c))为:匹配到以(i)结尾的子序列的串,后加一个字符c能够匹配到的子序列位置。我们直接(delta(i,c))为位置(i)之后第一次出现字符(c)的位置即可。因为贪心来讲,跳到最先出现的位置会更优,相当于给后面更多可能选项。
    如我们要在( exttt{aabacba})找到一个子序列与( ext{abc})匹配,那么跳的位置是1,3,5.
    构建算法很简单,直接从后往前扫描一遍,维护每个字符上一次出现的位置即可。注意实现上为了区分根节点和空状态,我们要把点的编号增加一位,如状态3对应的是结尾在第二位的子序列,1表示空串,0表示NULL。

    struct seqt {
    	//注意序列自动机的编号为实际位置+1
    	//1代表空串,2代表第1位,3代表第2位... 
    	int ch[maxn+5][maxc+5];
    	int last[maxc+5];//存储每个字符上一次出现的位置 
    	const int root=1;
    	inline int trans(int x,char c) {
    		return ch[x][c-'a'];
    	}
    	void build(char *s) {//无log做法 
    		int len=strlen(s+1);
    		for(int i=len;i>=1;i--){
    			//现在更新位置i对应的节点i+1 
    			for(int j=0;j<maxc;j++) ch[i+1][j]=last[j];
    			last[s[i]-'a']=i+1;
    		}
    		for(int j=0;j<maxc;j++) ch[1][j]=last[j];
    
    	}
    
    } S;
    

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    #define INF 0x3f3f3f3f
    #define maxn 2000
    #define maxc 26
    using namespace std;
    struct SAM {
    #define len(x) (t[x].len)
    #define link(x) (t[x].link)
    	struct node {
    		int ch[maxc];
    		int link;
    		int len;
    	} t[maxn*2+5];
    	const int root=1;
    	int last=root;
    	int ptr=1;
    	void extend(int c) {
    		int p=last,cur=++ptr;
    		len(cur)=len(p)+1;
    		while(t[p].ch[c]==0) {
    			t[p].ch[c]=cur;
    			p=link(p);
    		}
    		if(p==0) link(cur)=root;
    		else {
    			int q=t[p].ch[c];
    			if(len(p)+1==len(q)) link(cur)=q;
    			else {
    				int clo=++ptr;
    				link(clo)=link(q);
    				for(int i=0; i<maxc; i++) t[clo].ch[i]=t[q].ch[i];
    				len(clo)=len(p)+1;
    				link(q)=link(cur)=clo;
    				while(t[p].ch[c]==q) {
    					t[p].ch[c]=clo;
    					p=link(p);
    				}
    			}
    		}
    		last=cur;
    	}
    	void build(char *s) {
    		int len=strlen(s+1);
    		for(int i=1; i<=len; i++) extend(s[i]-'a');
    	}
    	inline int trans(int x,char c) {
    		return t[x].ch[c-'a'];
    	}
    } T;
    struct seqt {
    	//注意序列自动机的编号为实际位置+1
    	//1代表空串,2代表第1位,3代表第2位... 
    	int ch[maxn+5][maxc+5];
    	int last[maxc+5];//存储每个字符上一次出现的位置 
    	const int root=1;
    	inline int trans(int x,char c) {
    		return ch[x][c-'a'];
    	}
    	void build(char *s) {//无log做法 
    		int len=strlen(s+1);
    		for(int i=len;i>=1;i--){
    			//现在更新位置i对应的节点i+1 
    			for(int j=0;j<maxc;j++) ch[i+1][j]=last[j];
    			last[s[i]-'a']=i+1;
    		}
    		for(int j=0;j<maxc;j++) ch[1][j]=last[j];
    
    	}
    
    } S;
    
    int n,m;
    char a[maxn+5],b[maxn+5];
    int solve1() {
    	int ans=INF;
    	for(int i=1; i<=n; i++) {
    		int x=T.root;
    		for(int j=i; j<=n; j++) {
    			x=T.trans(x,a[j]);
    			if(x==0) {
    				ans=min(ans,j-i+1);
    				break;
    			}
    		}
    	}
    	if(ans==INF) ans=-1;
    	return ans;
    }
    int solve2() {
    	int ans=INF;
    	for(int i=1; i<=n; i++) {
    		int x=S.root;
    		for(int j=i; j<=n; j++) {
    			x=S.trans(x,a[j]);
    			if(x==0) {
    				ans=min(ans,j-i+1);
    				break;
    			}
    		}
    	}
    	if(ans==INF) ans=-1;
    	return ans;
    }
    int solve3() {
    	static int f[2][maxn*2+5];
    	//f[i][j]表示A串的前i位的子串中,能走到自动机上j节点的最短子串
    	//那么答案就是f[i][0](到空节点就是不匹配)
    	//第一维可以滚动数组
    	memset(f,0x3f,sizeof(f));
    	int now=0;
    	f[now][T.root]=0;
    	for(int i=1; i<=n; i++) {
    		now^=1;
    		f[now][T.root]=0;
    		for(int j=1; j<=T.ptr; j++) f[now][j]=f[now^1][j];
    		for(int j=1; j<=T.ptr; j++) {
    			int to=T.trans(j,a[i]);
    			f[now][to]=min(f[now][to],f[now^1][j]+1);
    		}
    	}
    	if(f[now][0]==INF) return -1;
    	else return f[now][0];
    }
    int solve4() {
    	static int f[2][maxn*2+5];
    	memset(f,0x3f,sizeof(f));
    	int now=0;
    	f[now][1]=0;
    	for(int i=1; i<=n; i++) {
    		now^=1;
    		f[now][1]=0;
    		for(int j=1; j<=m+1; j++) f[now][j]=f[now^1][j];
    		for(int j=1; j<=m+1; j++) {
    			int to=S.trans(j,a[i]);
    			f[now][to]=min(f[now][to],f[now^1][j]+1);
    		}
    	}
    	if(f[now][0]==INF) return -1;
    	else return f[now][0];
    }
    int main() {
    	scanf("%s",a+1);
    	scanf("%s",b+1);
    	n=strlen(a+1);
    	m=strlen(b+1);
    	T.build(b);
    	S.build(b);
    	printf("%d
    ",solve1());
    	printf("%d
    ",solve2());
    	printf("%d
    ",solve3());
    	printf("%d
    ",solve4());
    }
    /*
    hack:
    abaaa
    aabaa
    */
    
  • 相关阅读:
    第二阶段冲刺第七天,6月6日。
    第二阶段冲刺第六天,6月5日。
    第二阶段冲刺第五天,6月4日。
    第二阶段冲刺第四天,6月3日。
    第二阶段冲刺第三天,6月2日。
    第二阶段冲刺第二天,6月1日。
    垃圾收集器与内存分配策略(1)
    OutOfMemoryError异常
    对象访问
    java内存区域与内存溢出异常(2)
  • 原文地址:https://www.cnblogs.com/birchtree/p/12740669.html
Copyright © 2011-2022 走看看