• 【LOJ #6198】谢特


    Description

    LOJ #6198

    给出一个长度为 (n) 的,仅包含小写字母的字符串 (s)

    定义这个字符串以第 (i) 个字符开头的后缀为后缀 (i)(编号从 (1) 开始),每个后缀 (i) 都有一个权值 (w_i),同时定义两个后缀 (i, j (i eq j)) 的贡献为它们的最长公共前缀长度加上它们权值的异或和,也就是 ( ext{LCP}(i, j) + (w_i ext{xor} w_j))

    而你的任务就是,求出这个字符串的所有后缀两两之间贡献的最大值。

    数据范围:(1 leq n leq 10^5)(0 leq w_i < n)

    Solution

    算法一:SA + 可持久化 0/1 trie

    对字符串 (s) 做一遍 ( ext{SA}),再将 ( ext{height}) 数组求出。

    为了方便求解,我们求出一个新的后缀权值序列 ({ w'_i }),满足 (w'_i = w_{ ext{SA}_i})

    那么,后缀 ( ext{SA}_i, ext{SA}_j(i < j)) 的贡献即为:

    [minlimits_{i < k leq j} left{ ext{height}_k ight} + left(w'_i ext{xor} w'_j ight) ]

    那么现在就是要求出所有满足 (1 leq i < j leq n) 的数对 ((i, j)) 所计算出的上式最大值。
    这就是一个经典老题了,考虑分治。

    定义分治函数 ( ext{solve}(l, r)),表示计算 (l leq i < j leq r) 时的答案。

    取分治中心 ( ext{mid}) 为区间 ((l, r])( ext{height}) 值最小的一点。
    至于计算 ( ext{mid}) 可以使用 ST 表。

    那么现在只需要计算出所有满足 (l leq i < ext{mid} leq j leq r) 的数对 ((i, j)) 的贡献最大值。
    随后调用 ( ext{solve}(l, ext{mid} - 1))( ext{solve}( ext{mid}, r)) 即可。

    对于所有满足 (l leq i < ext{mid} leq j leq r) 的数对 ((i, j)),一定有 (minlimits_{i < k leq j} left{ ext{height}_k ight} = ext{height}_ ext{mid})
    此时贡献表达式的第一个参数已经确定下来,现在就是要求 (left(w'_i ext{xor} w'_j ight)) 的最大值。

    对于分治中心 ( ext{mid}) 分出来的两个区间 ([l, ext{mid}))([ ext{mid}, r]),我们选择长度较小的一段区间,穷举该区间里的每一个点,求出该点与另一段区间中的每个点的异或最大值来更新答案,使用可持久化 0/1 trie 维护即可。

    我们如果将分治的过程倒过来看,那么 " 穷举较小区间内的所有点 " 其实是一个启发式合并。
    每个点至多被枚举了 (log_2 n) 次。故总时间复杂度为 (mathcal{O}(n log^2 n))

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 100100;
    
    int n, m = 256;
    char s[N];
    
    int temp[N], w[N];
    
    int SA[N], rk[N];
    int cnt[N], id[N], oldrk[N * 2], px[N];
    
    bool cmp(int x, int y, int w) {
    	return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
    }
    
    int height[N];
    
    int logx[N];
    int f[N][17];
    
    int calc(int l, int r) {
    	int k = logx[r - l + 1];
    
    	if (height[f[l][k]] <= height[f[r - (1 << k) + 1][k]])
    		return f[l][k];
    	else
    		return f[r - (1 << k) + 1][k];
    }
    
    int cT, root[N];
    struct trie {
    	int ch[2];
    	int cnt;
    } t[N * 32];
    
    void insert(int &p, int now, int dep, int val) {
    	p = ++ cT;
    	t[p] = t[now];
    	t[p].cnt ++;
    	if (dep < 0) return;
    	int v = val >> dep & 1;
    	insert(t[p].ch[v], t[now].ch[v], dep - 1, val);
    }
    
    int ask(int p, int q, int dep, int val) {
    	if (dep < 0) return 0;
    	int v = val >> dep & 1;
    	int cnt = t[t[q].ch[v ^ 1]].cnt - t[t[p].ch[v ^ 1]].cnt;
    	if (cnt)
    		return ask(t[p].ch[v ^ 1], t[q].ch[v ^ 1], dep - 1, val) + (1 << dep);
    	else
    		return ask(t[p].ch[v], t[q].ch[v], dep - 1, val);
    }
    
    int ans;
    
    void solve(int l, int r) {
    	if (l == r) return;
    
    	int mid = calc(l + 1, r);
    
    	if (mid - l <= r - mid + 1) {
    		for (int i = l; i < mid; i ++)
    			ans = max(ans, height[mid] + ask(root[mid - 1], root[r], 17, w[i]));
    	} else {
    		for (int i = mid; i <= r; i ++)
    			ans = max(ans, height[mid] + ask(root[l - 1], root[mid - 1], 17, w[i]));
    	}
    
    	solve(l, mid - 1), solve(mid, r);
    }
    
    int main() {
    	scanf("%d", &n);
    	scanf("%s", s + 1);
    
    	for (int i = 1; i <= n; i ++)
    		scanf("%d", &temp[i]);
    
    	for (int i = 1; i <= n; i ++) rk[i] = s[i];
    	for (int i = 1; i <= n; i ++) cnt[rk[i]] ++;
    	for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
    	for (int i = n; i >= 1; i --) SA[cnt[rk[i]] --] = i; 
    
    	for (int w = 1, p = 0; w < n; w <<= 1, m = p) {
    		p = 0;
    		for (int i = n - w + 1; i <= n; i ++) id[++ p] = i;
    		for (int i = 1; i <= n; i ++)
    			if (SA[i] > w) id[++ p] = SA[i] - w;
    
    		for (int i = 0; i <= m; i ++) cnt[i] = 0;
    		for (int i = 1; i <= n; i ++) cnt[px[i] = rk[id[i]]] ++;
    		for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
    		for (int i = n; i >= 1; i --) SA[cnt[px[i]] --] = id[i];
    
    		p = 0;
    		for (int i = 1; i <= n; i ++) oldrk[i] = rk[i];
    		for (int i = 1; i <= n; i ++)
    			rk[SA[i]] = cmp(SA[i - 1], SA[i], w) ? p : ++ p;
    	}
    
    	for (int i = 1, H = 0; i <= n; i ++) {
    		if (H) H --;
    		while (s[i + H] == s[SA[rk[i] - 1] + H]) H ++;
    		height[rk[i]] = H;
    	}
    
    	logx[0] = -1;
    	for (int i = 1; i <= n; i ++)
    		logx[i] = logx[i >> 1] + 1;
    
    	for (int i = 1; i <= n; i ++)
    		f[i][0] = i;
    
    	for (int j = 1; j <= 16; j ++)
    		for (int i = 1; i + (1 << j) - 1 <= n; i ++) {
    			if (height[f[i][j - 1]] <= height[f[i + (1 << (j - 1))][j - 1]])
    				f[i][j] = f[i][j - 1];
    			else
    				f[i][j] = f[i + (1 << (j - 1))][j - 1];
    		}
    
    	for (int i = 1; i <= n; i ++)
    		w[i] = temp[SA[i]];
    
    	for (int i = 1; i <= n; i ++)
    		insert(root[i], root[i - 1], 17, w[i]);
    
    	solve(1, n);
    
    	printf("%d
    ", ans);
    
    	return 0;
    }
    

    算法二:SAM + 0/1 trie 合并

    (待填 ......)

    keep the love forever.
  • 相关阅读:
    UDP——python网络编程笔记
    2018.7计划
    二叉树
    第7.5章:类——Cookbook笔记
    网络编程 Cookbook
    网络编程 csapp
    第5.5章 迭代器与生成器——CookBook笔记
    第3.5章 数据结构与算法——CookBook笔记
    关于排序
    关于搜索
  • 原文地址:https://www.cnblogs.com/cjtcalc/p/14617847.html
Copyright © 2020-2023  润新知