zoukankan      html  css  js  c++  java
  • ICPC2021 沈阳站 M String Problem

    牛客传送门


    KMP的做法暂时没看懂,这里提供两种SAM的做法。
    感谢樱花猪开喵喵车创大白熊新手上路两队的代码提供的思路。


    第一种做法稍麻烦一些:

    对于每一个前缀,字典序最大的子串一定是该前缀的一个后缀,而比较这些后缀的方法就是选择这些后缀中,最靠前的不同的字符。如果将原串反过来,就可以用SAM维护了。

    将反串建成SAM,然后对于后缀链接树上每一个节点\(u\)的出边\(v_i\),按\(endpos[v] - len[u]\)在原串中的字符排序,这样就能优先访问字典序更大的子串了。

    现在对于每个前缀都要求对应的最大后缀。可以倒着做:先将所有节点以dfs序为关键字扔到一个大根堆中,因为dfs序大的节点代表的子串一定大,那么如果当前堆顶代表的子串在枚举的当前前缀的范围内,那么这个子串就是答案,否则将堆顶弹出,再取堆中最大的元素。

    这样时间复杂度是\(O(n \log n)\),需要稍微加一些常数优化才能通过。

    #include<bits/stdc++.h>
    using namespace std;
    #define enter puts("") 
    #define space putchar(' ')
    #define Mem(a, x) memset(a, x, sizeof(a))
    #define In inline
    #define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
    typedef long long ll;
    typedef double db;
    const int INF = 0x3f3f3f3f;
    const db eps = 1e-8;
    const int maxn = 1e6 + 5;
    const int maxs = 27;
    In ll read()
    {
    	ll ans = 0;
    	char ch = getchar(), las = ' ';
    	while(!isdigit(ch)) las = ch, ch = getchar();
    	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
    	if(las == '-') ans = -ans;
    	return ans;
    }
    In void write(ll x)
    {
    	if(x < 0) x = -x, putchar('-');
    	if(x >= 10) write(x / 10);
    	putchar(x % 10 + '0');
    }
    
    int n, ans[maxn];
    char s[maxn];
    struct Sam
    {
    	int tra[maxn << 1][maxs], link[maxn << 1], len[maxn << 1], endp[maxn << 1], cnt, las;
    	In void init() {link[cnt = las = 0] = -1; Mem(tra[0], 0);}
    	In void insert(int c, int id)
    	{
    		int now = ++cnt, p = las; Mem(tra[now], 0);
    		len[now] = len[p] + 1, endp[now] = id;
    		while(~p && !tra[p][c]) tra[p][c] = now, p = link[p];
    		if(p == -1) link[now] = 0;
    		else
    		{
    			int q = tra[p][c];
    			if(len[q] == len[p] + 1) link[now] = q;
    			else
    			{
    				int clo = ++cnt;
    				memcpy(tra[clo], tra[q], sizeof(tra[q]));
    				len[clo] = len[p] + 1, endp[clo] = endp[q];
    				link[clo] = link[q], link[q] = link[now] = clo;
    				while(~p && tra[p][c] == q) tra[p][c] = clo, p = link[p];
    			}
    		}
    		las = now;
    	}
    	#define pr pair<int, int>
    	#define mp make_pair
    	#define F first
    	#define S second
    	int buc[maxn << 1], pos[maxn << 1];
    	vector<pr> V[maxn << 1];
    	int du[maxn << 1], dfn[maxn << 1], dcnt;
    	In void dfs(int now)
    	{
    		dfn[now] = ++dcnt;
    		for(auto x : V[now]) dfs(x.S);
    	}
    	In void buildGraph()
    	{
    		for(int i = 1; i <= cnt; ++i) buc[len[i]]++;
    		for(int i = 1; i <= cnt; ++i) buc[i] += buc[i - 1];
    		for(int i = 1; i <= cnt; ++i) pos[buc[len[i]]--] = i;
    		endp[0] = INF;
    		for(int i = cnt; i; --i)
    		{
    			int now = pos[i], fa = link[now];
    			du[fa]++;
    			endp[fa] = min(endp[fa], endp[now]);
    			V[fa].push_back(mp(s[endp[now] + len[fa]], now));
    		}
    		for(int i = 0; i <= cnt; ++i) sort(V[i].begin(), V[i].end());
    		dcnt = 0, dfs(0);
    	}
    	In void solve()
    	{
    		priority_queue<pr> q;
    		for(int i = 1; i <= cnt; ++i) if(!du[i]) q.push(mp(dfn[i], i));
    		for(int i = n, now = 0; i; --i)
    		{
    			while(!ans[i])
    			{
    				if(!now) now = q.top().S;		//减少堆操作来优化常数 
    				if(endp[now] + len[link[now]] > i)
    				{
    					q.pop();
    					if(now && !--du[link[now]]) q.push(mp(dfn[link[now]], link[now]));
    					now = 0;
    				}
    				else ans[i] = endp[now];
    			}
    		}
    	}
    }S;
    
    int main()
    {
    	scanf("%s",s + 1);
    	n = strlen(s + 1); S.init();
    	for(int i = n; i; --i) S.insert(s[i] - 'a', i);
    	S.buildGraph(), 
    	S.solve();
    	for(int i = 1; i <= n; ++i) write(ans[i]), space, write(i), enter;
    	return 0;
    }
    

    第二种做法代码量相对来说短了不少,我认为是对暴力的一种优化。

    首先这题一种\(O(n^2)\)的暴力做法是取出所有子串,并按字典序总大到小排序,记一个子串是\(S_{l \sim r}\),那么\(ans[r]\)的答案就是第一个出现的\(S_{l \sim r}\)

    用SAM优化这个方法:用正串建完SAM后,贪心的在SAM上跑字典序最大的子串,那么第一个走到该节点的子串一定是最大的。又因为在同一个节点的子串结束位置相同,而且经过这个节点到达别的节点形成的子串前缀相同,所以后来经过这个节点形成的子串一定比第一次经过的要小,那么走过的节点就不用再走了。

    时间复杂度就是\(O(27n)\).

    #include<bits/stdc++.h>
    using namespace std;
    #define enter puts("") 
    #define space putchar(' ')
    #define Mem(a, x) memset(a, x, sizeof(a))
    #define In inline
    #define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
    typedef long long ll;
    typedef double db;
    const int INF = 0x3f3f3f3f;
    const db eps = 1e-8;
    const int maxn = 1e6 + 5;
    const int maxs = 27;
    In ll read()
    {
    	ll ans = 0;
    	char ch = getchar(), las = ' ';
    	while(!isdigit(ch)) las = ch, ch = getchar();
    	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
    	if(las == '-') ans = -ans;
    	return ans;
    }
    In void write(ll x)
    {
    	if(x < 0) x = -x, putchar('-');
    	if(x >= 10) write(x / 10);
    	putchar(x % 10 + '0');
    }
    
    int n, ans[maxn];
    char s[maxn];
    struct Sam
    {
    	int tra[maxn << 1][maxs], link[maxn << 1], len[maxn << 1], endp[maxn << 1], cnt, las;
    	In void init() {link[cnt = las = 0] = -1; Mem(tra[0], 0);}
    	In void insert(int c, int id)
    	{
    		int now = ++cnt, p = las; Mem(tra[now], 0);
    		len[now] = len[p] + 1, endp[now] = id;
    		while(~p && !tra[p][c]) tra[p][c] = now, p = link[p];
    		if(p == -1) link[now] = 0;
    		else
    		{
    			int q = tra[p][c];
    			if(len[q] == len[p] + 1) link[now] = q;
    			else
    			{
    				int clo = ++cnt;
    				memcpy(tra[clo], tra[q], sizeof(tra[q]));
    				len[clo] = len[p] + 1, endp[clo] = endp[q];
    				link[clo] = link[q], link[q] = link[now] = clo;
    				while(~p && tra[p][c] == q) tra[p][c] = clo, p = link[p];
    			}
    		}
    		las = now;
    	}
    	bool vis[maxn << 1];
    	In void dfs(int now, int l)				//l:最大子串开始位置 
    	{
    		vis[now] = 1;
    		for(int i = 25; i >= 0; --i)		//在SAM贪心地走最大的 
    			if(tra[now][i] && !vis[tra[now][i]]) dfs(tra[now][i], l + 1);
    		if(!ans[endp[now]]) ans[endp[now]] = endp[now] - l + 1;
    	}
    }S;
    
    int main()
    {
    	scanf("%s",s + 1);
    	n = strlen(s + 1); S.init();
    	for(int i = 1; i <= n; ++i) S.insert(s[i] - 'a', i);
    	S.dfs(0, 0);
    	for(int i = 1; i <= n; ++i) write(ans[i]), space, write(i), enter;
    	return 0;
    }
    

    还有一个就是kmp的做法,我虽然没看懂,不过也发一下代码吧。

    #include<bits/stdc++.h>
    using namespace std;
    #define enter puts("") 
    #define space putchar(' ')
    #define Mem(a, x) memset(a, x, sizeof(a))
    #define In inline
    #define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
    typedef long long ll;
    typedef double db;
    const int INF = 0x3f3f3f3f;
    const db eps = 1e-8;
    const int maxn = 1e6 + 5;
    const int maxs = 27;
    In ll read()
    {
    	ll ans = 0;
    	char ch = getchar(), las = ' ';
    	while(!isdigit(ch)) las = ch, ch = getchar();
    	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
    	if(las == '-') ans = -ans;
    	return ans;
    }
    In void write(ll x)
    {
    	if(x < 0) x = -x, putchar('-');
    	if(x >= 10) write(x / 10);
    	putchar(x % 10 + '0');
    }
    
    int n;
    char s[maxn];
    
    vector<int> f, g;
    
    int main()				//好短 
    {
    	scanf("%s",s + 1);
    	n = strlen(s + 1);
    	for(int i = 1; i <= n; ++i)
    	{
    		g.clear(), g.push_back(i);
    		for(auto x : f)
    		{
    			while(!g.empty() && s[x + i - g.back()] > s[i]) g.pop_back();
    			if(g.empty() || s[x + i - g.back()] == s[i]) g.push_back(x);
    		}
    		f.clear();
    		for(auto x : g)
    		{
    			while(!f.empty() && (i - f.back() + 1) * 2 > i - x + 1) f.pop_back();
    			f.push_back(x);
    		}
    		write(f.back()), space, write(i), enter;
    	}
    	return 0;
    }
    
  • 相关阅读:
    iOS nsstring 截取字符前后字符串
    iOS 计算时间差
    Android的ProgressBar
    Android:OptionMenu
    eclipse the user operation is waiting for building workspace" to complete
    Android存储之SQLiteDatbase
    Android保存之SharedPreferences
    pkg_utility
    Oracle同义词 synonyms
    xzzx
  • 原文地址:https://www.cnblogs.com/mrclr/p/15594484.html
Copyright © 2011-2022 走看看