• BZOJ4516: [Sdoi2016]生成魔咒


    题目地址

    题目地址

    题解

    很棒的一道后缀数组题!
    其实就是对原串求出每一个前缀的本质不同子串的个数。
    求一个串的本质不同子串数是一个经典问题,其为(frac {n imes (n + 1)} 2 - sum ext{height[i]})
    考虑插入前缀的过程,这种情况下的( ext {sa})数组和( ext {height})数组是在不断改变的,这样就很不好统计了。
    那么不妨把原串翻转过来,这样加入一个字符其实就是加入一个新的后缀而已。
    而我们知道( ext{LCP(x,y)} = min_{k=x+1}^y { ext{height[k]}}),所以在后缀没有加全的情况下其实也是可以知道两个串的( ext {LCP})的,只需要预处理一个( ext{ST})表即可。
    考虑一下加入一个后缀对当前情况下( ext {height})数组的影响,在进行一次后缀排序后,假设其位置为(pos),那么只会影响到( ext {height[pos]})( ext {height[pos + 1]})
    所以只需要动态修改这两个东西即可。
    这两个东西可以用平衡树来维护。具体的,在每次插入一个数后,找出它的前驱和后继,给(sum ext{height[i]})加上( ext{LCP}(pre, pos))( ext{LCP}(pos, next))
    平衡树可以上( ext{set})

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int N = 100010;
    
    ll ans = 0;
    int n, m, a[N], b[N], f[N][18], LG[N];
    int sa[N], height[N], rnk[N], tp[N], tong[N];
    set<int> s;
    
    void radix_sort() {
    	for(int i = 1; i <= m; ++i) tong[i] = 0;
    	for(int i = 1; i <= n; ++i) tong[rnk[i]]++;
    	for(int i = 1; i <= m; ++i) tong[i] += tong[i - 1];
    	for(int i = n; i; --i) sa[tong[rnk[tp[i]]]--] = tp[i];
    }
    
    void suffix_sort() {
    	for(int i = 1; i <= n; ++i) rnk[i] = a[i], tp[i] = i;
    	m = n; radix_sort();
    	for(int w = 1, p = 1; p < n && w <= n; w <<= 1, m = p) {
    		p = 0; 
    		for(int i = 1; i <= w; ++i) tp[++p] = n - w + i;
    		for(int i = 1; i <= n; ++i) if(sa[i] > w) tp[++p] = sa[i] - w;
    		radix_sort(); swap(tp, rnk); rnk[sa[1]] = p = 1;
    		for(int i = 2; i <= n; ++i) 
    			rnk[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++p;
    	}
    	for(int i = 1, k = 0; i <= n; ++i) {
    		if(k) --k;
    		int j = sa[rnk[i] - 1];
    		while(a[i + k] == a[j + k] && i + k <= n && j + k <= n) ++k;
    		height[rnk[i]] = k; 
    	}
    }
    
    int query(int l, int r) {
    	int k = LG[r - l + 1];
    	return min(f[l][k], f[r - (1 << k) + 1][k]);
    }
    
    int main() {
    	scanf("%d", &n);
    	for(int i = 1; i <= n; ++i) scanf("%d", &a[i]), b[i] = a[i];
    	sort(b + 1, b + n + 1);
    	for(int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
    	reverse(a + 1, a + n + 1); suffix_sort();
    	LG[2] = 1;
    	for(int i = 3; i <= n; ++i) LG[i] = LG[i >> 1] + 1;
    	for(int j = 1; j <= n; ++j) f[j][0] = height[j];
    	for(int j = 1; j <= 19; ++j) {
    		for(int i = 1; i + (1 << j) - 1 <= n; ++i) 
    			f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
    	}
    	ans = 0;
    	for(int i = n; i; --i) {
    		set<int>::iterator it = s.insert(rnk[i]).first, p1 = it, p2 = it;
    		if(it != s.begin()) {
    			p1--; 
    			ans += query(*p1 + 1, rnk[i]); 
    			if(++p2 != s.end()) {
    				ans += query(rnk[i] + 1, *p2);
    				ans -= query(*p1 + 1, *p2);
    			}
    		} else if(++p2 != s.end()) 
    			ans += query(rnk[i] + 1, *p2);
    		printf("%lld
    ", 1LL * (n - i + 1) * (n - i + 2) / 2LL - ans);
    	}
    	return 0;
    } 
    
  • 相关阅读:
    已开启博客园~
    友链
    javacv 接收RTSP流(或avi/mp4视频文件),输出byte[]
    Springboot项目中,使用logback来管理日志。
    PPT文件流转为图片,并压缩成ZIP文件输出到指定目录
    通过AOP自定义注解实现记录用户操作日志。
    使用javacv,解码socket接收的H264码流(byte[]),转为yuv处理,最后再合成转为H264
    idea 开始java之旅
    浅谈Winform控件开发(一):使用GDI+美化基础窗口
    WinformGDI+入门级实例——扫雷游戏(附源码)
  • 原文地址:https://www.cnblogs.com/henry-1202/p/12045734.html
Copyright © 2020-2023  润新知