zoukankan      html  css  js  c++  java
  • [HEOI2015]最短不公共子串

    题意:

    洛谷链接

    给出两个字符串 (A)(B),分别求出:
    1.最短的串,是 (A) 的子串,不是 (B) 的子串。
    2.最短的串,是 (A) 的子串,不是 (B) 的子序列。
    3.最短的串,是 (A) 的子序列,不是 (B) 的子串。
    4.最短的串,是 (A) 的子序列,不是 (B) 的子序列。


    一道四合一题,如果分开做前两个好说,后两个得有点操作 ,不过由于我太菜做不得。而且暂时还不想学后两个点单独怎么做。

    那我们把这四个看成一类问题。

    考虑自动机:一个节点表示一个状态,每条边表示状态的转移。由于子串和子序列都能建出自动机来,那么我们只需要对两个自动机进行相同的转移,则 (A) 中有而 (B) 中没有的状态就是我们要找的答案。

    子序列建成自动机这个应该都会,由于希望最长的匹配,肯定相同的字符越靠前越好。每个位置是一个点,向后每个字符往离它最近的点连边,时间复杂度与空间复杂度均为 (O(n*) 字符集大小())

    子串建成自动机可以选择前缀自动机也可选择后缀自动机,而这题数据范围是 (2000),因为复杂度瓶颈并不在这里,当然如果不想考虑空间的话可以写 (sam)。我们这里只需要用 (trie) 当自动机就可以了,把每个子串取出来建立一个 (trie),字符总长度 (O(n^2)),时间复杂度与空间复杂度均为 (O(n^2*字符集大小))。看着大算算其实可过。

    那么自动机就建好了,每次询问是什么,就把两个字符串建成相应的自动机就好了。那么现在考虑如何找答案。

    因为一条边相当于加进去一个字符,我们进行 (BFS),遇到第一个 (A) 中存在而 (B) 中不存在的状态就把长度 (+1) 输出出来。如果遍历 (A) 的完整个自动机(自动机肯定不会有环的),还没有找到一个 (B) 中没有的状态,那 (A) 就太逊了,输出 (-1)

    考虑 (BFS) 复杂度:(trie) 进行 (BFS) 时,由于 (trie) 是像树一样的结构,每个点只会被遍历一次,复杂度不会超过 (O(n^2)) 。而当两个序列自动机进行遍历的时候,由于到达一个点可以通过不同的路径,如果无脑加点会被卡成指数级。我们需要用 (vis) 数组记录一下哪些状态到达过,由于序列自动机节点有 (n) 个,这样时间复杂度与空间复杂度也都是 (O(n^2)) 的,而且这个复杂度瓶颈不可避,前面自然也就不需要那么优了。

    不过要注意只有两个自动机同时为序列自动机的时候才用 (vis),否则 (trie) 那么多点也开不下是吧~

    #include <bits/stdc++.h>
    using namespace std;
    const int N=101010;
    const int qwq=2000100;
    const int inf=0x3f3f3f3f;
    
    struct T {
    
    	char s[N];
    	int n;
    	int ch[qwq][26],cnt,now,rt;
    
    	void trie() {
    		memset(ch,0,sizeof(ch));
    		rt = now = cnt = 1;
    		for(int i=1;i<=n;i++) {
    			now = rt;
    			for(int j=i;j<=n;j++) {
    				int c = s[j] - 'a';
    				if(!ch[now][c]) ch[now][c] = ++cnt;
    				now = ch[now][c];
    			}
    		}
    	}
    
    	void lie() {
    		memset(ch,0,sizeof(ch));
    		cnt = n; rt = 0;
    		for(int i=1;i<=n;i++) {
    			for(int j=i-1;j>=0;j--) {
    				ch[j][ s[i]-'a' ] = i;
    				if(s[j]==s[i]) break;
    			}
    		}
    	}
    
    } A,B;
    
    struct E{ int x,y,le; };
    queue <E> q;
    bool vis[2333][2333];
    
    int query() {
    	bool flag = 0;
    	if(A.rt==0 && B.rt==0) flag = 1;
    	q.push( (E){A.rt,B.rt,0} );
    	vis[A.rt][B.rt] = 1;
    	while(!q.empty()) {
    		E now = q.front(); q.pop();
    		for(int i=0;i<25;i++) {
    			int u = A.ch[now.x][i], v = B.ch[now.y][i];
    			if(flag) { if(vis[u][v]) continue; }
    			if(!u) continue;
    			if(u && !v) { while(!q.empty()) q.pop(); return now.le+1; }
    			q.push( (E){u,v,now.le+1} );
    			if(flag) vis[u][v] = 1;
    		}
    	}
    	return -1;
    }
    
    int main() {
    	int ans4;
    	scanf("%s%s",A.s+1,B.s+1);
    	A.n = strlen(A.s+1); B.n = strlen(B.s+1);
    	A.trie(); B.trie(); cout<<query()<<"
    ";
    	B.lie();            cout<<query()<<"
    ";
    	A.lie();            ans4 = query();
    	B.trie();           cout<<query()<<"
    "<<ans4;
    	return 0;
    }
    

    (sam) 的做法:

    后缀自动机时间建立的复杂度是 (O(n)) 的,空间复杂度是 (O(n*26)) 的,这让人很舒服,但是由于到达一个点同样有多条路径,我们每次 (BFS) 都要记录 (vis)

    要注意 (sam) 数组要开两倍。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=101010;
    const int qwq=2000100;
    const int inf=0x3f3f3f3f;
    
    struct T {
    
    	char s[2333];
    	int n;
    	int ch[4333][26],fa[4333],len[4333],cnt,now,rt;
    
    	void insert(int c) {
    		int u = now; now = ++cnt;
    		len[cnt] = len[u] + 1;
    		for(; u&&!ch[u][c]; u=fa[u]) ch[u][c] = cnt;
    		if(!u) fa[cnt] = 1;
    		else {
    			int zjt = ch[u][c];
    			if(len[zjt]==len[u]+1) fa[cnt] = zjt;
    			else {
    				int ob = cnt+1;
    				memcpy(ch[ob],ch[zjt],sizeof(ch[zjt]));
    				fa[ob] = fa[zjt]; len[ob] = len[u] + 1;
    				fa[cnt] = fa[zjt] = ob;
    				for(; u&&ch[u][c]==zjt; u=fa[u]) ch[u][c] = ob;
    				cnt++;
    			}
    		}
    	}
    
    	void sam() {
    		memset(ch,0,sizeof(ch));
    		now = cnt = rt = 1;
    		for(int i=1;i<=n;i++) insert(s[i]-'a');
    	}
    
    	void lie() {
    		memset(ch,0,sizeof(ch));
    		cnt = n; rt = 0;
    		for(int i=1;i<=n;i++) {
    			for(int j=i-1;j>=0;j--) {
    				ch[j][ s[i]-'a' ] = i;
    				if(s[j]==s[i]) break;
    			}
    		}
    	}
    
    } A,B;
    
    struct E{ int x,y,le; };
    queue <E> q;
    bool vis[4333][4333];
    
    int query() {
    	memset(vis,0,sizeof(vis));
    	q.push( (E){A.rt,B.rt,0} );
    	vis[A.rt][B.rt] = 1;
    	while(!q.empty()) {
    		E now = q.front(); q.pop();
    		for(int i=0;i<25;i++) {
    			int u = A.ch[now.x][i], v = B.ch[now.y][i];
    			if(vis[u][v] || !u) continue;
    			if(u && !v) { while(!q.empty()) q.pop(); return now.le+1; }
    			q.push( (E){u,v,now.le+1} );
    			vis[u][v] = 1;
    		}
    	}
    	return -1;
    }
    
    int main() {
    	int ans4;
    	scanf("%s%s",A.s+1,B.s+1);
    	A.n = strlen(A.s+1); B.n = strlen(B.s+1);
    	A.sam(); B.sam(); cout<<query()<<"
    ";
    	B.lie();            cout<<query()<<"
    ";
    	A.lie();            ans4 = query();
    	B.sam();           cout<<query()<<"
    "<<ans4;
    	return 0;
    }
    
  • 相关阅读:
    OpenCVPython系列之相机校准
    matplotlib的使用——坐标轴设置部分
    nvcc fatal : Unsupported gpu architecture ‘compute_30‘
    ubuntu中aptget install 改用阿里云作为数据源
    OpenCVPython系列之稠密光流
    OpenCVPython系列之Camshift算法
    安装cudnn
    OpenCVPython系列之相机校准实践
    matplotlib的使用——legend图例的设置
    matplotlib的使用——picture in picture画中画的使用
  • 原文地址:https://www.cnblogs.com/clever-sheep/p/12884333.html
Copyright © 2011-2022 走看看