大二寒假第三周学习笔记

周一

后缀自动机好难理解啊,看了一上午,感觉理解了一半吧……边做题边加深理解吧

这两篇博客讲的不错,下面写很多是这两篇博客的内容

史上最通俗的后缀自动机详解 - KesdiaelKen 的博客 - 洛谷博客

浅谈后缀自动鸡/SAM - Arextre 的博客 - 洛谷博客

后来又在b站上看到不错的讲解 按照视频说的当作黑盒使用吧,深究原理好痛苦

史上最易懂的后缀自动机讲解!独创理解思路还有例题讲解~_哔哩哔哩_bilibili

本质上是将一个字符串的所有子串的信息聚集在一个有向无环图中

一开始有一个源点,一条边代表在末尾添加一个字符

每一个节点代表一个endpos等价类,一个类中有很多个子串,它们长度连续,且短的为长的后缀

一条路径所形成的字符串一定在终点节点的等价类中

一个点的父亲是另一类边(有两类边),代表当前等价类的,父亲类所代表的子串是儿子类所代表字串的后缀

建出一个trie树,到达一个点又很多路径,这个点包含了所有到达这个点的所有路径形成的字符串

还建立一个fail树,fail指针指向当前字符串的后缀中最长的后缀所在的点

P3804 【模板】后缀自动机 (SAM)

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 2e6 + 10; 						//字符串总长两倍
int t[N][26], len[N], fa[N], c[N], k[N];  		//t为自动机上的边,即fail,fa为parent tree上的边
int siz[N], cnt = 1, last = 1, n;               //len表示节点i的类中最长串的长度
vector<int> endpos[N];
char s[N];
ll ans;
 
void add(int c, int pos) //题目不同更新u,r两处 不只有小写字母也要改
{
	int p = last;
	int u = last = ++cnt;    //u是新建的节点
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;    //r是多建的虚空点
			_for(i, 0, 25) t[r][i] = t[q][i];          //有些题不只是小写字母 注意
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
			//更新r


		}
	}

	//更新u
	siz[u] = 1;   					//注意siz的初始化只有siz[u] = 1  不要写siz[q] = 1
	endpos[u].push_back(pos);       //siz表示有多少个终止位置,也是子串出现次数
}
 
int main()
{
	scanf("%s", s + 1);
	n = strlen(s + 1);
	_for(i, 1, n) add(s[i] - 'a', i);
 
	_for(i, 1, cnt) c[len[i]]++;               //根据len桶排序 也可以dfs求siz数组
	_for(i, 1, cnt) c[i] += c[i - 1];
	_for(i, 1, cnt) k[c[len[i]]--] = i;
	for(int i = cnt; i >= 1; i--)
	{
		int id = k[i];
		siz[fa[id]] += siz[id];
		for(int x: endpos[id]) endpos[fa[id]].push_back(x);
	}

	_for(i, 1, n)
		if(siz[i] > 1)
			ans = max(ans, 1LL * siz[i] * len[i]);
	printf("%lld\n", ans);
 
	return 0;
}

应用

1.求一个串是否为另一个串的字串

SAM就是集合了一个串所有子串的信息

建立SAM,然后在SAM上跑就行

没有跑到空就是子串

用SA的话,要把两个串拼在一起,查看height数组。是否有height值等于串长度且在两个串中。

2.两个串的最长公共子串

SAM:一个建SAM,另一个在SAM上跑,看能跑多长,跑不了就往父亲跑

SA:拼在一起,遍历height数组,找在两个串中且最大的值

3.不同字串个数

SAM:遍历每个点,它代表的字符串个数就是len[i] - len[fa[i]] 遍历每个点即可。或者统计从开始节点走有多少个不同路径,有向无环图上dp即可,f[i]表示从i节点出发的路径数

SA:遍历每一个后缀,贡献为当前后缀长度减去重复的height

周二

poj 3415(后缀自动机)

好题,妙啊

我开始觉得可以用后缀数组写,按照height分组,但是发现同一组内的两个后缀统计答案的时候要O(n^2) 没有想到什么优化的方法。不过看到有人是用后缀数组做的,用单调栈优化。

这题用后缀自动机会简单粗暴一点

首先对a串建立后缀自动机,然后b串在这上面跑

跑到一个点时,其实在a串上就对应跑到小于等于当前长度的所有后缀

这时候要分两部分,一个是fail的部分,这部分是可以全部包含的,一个是当前点的部分,这部分只有小于等于当前长度的后缀是可以跑到的,并不能跑到这个点所代表的字符串的所有集合

对于fail的部分,我们用一个数组记录一下,最后在parent tree上求子树和,类似求子串出现的次数,只不过这里是求b串在自动机上出现的次数,而不是a串自己。

第二部分是当前部分的统计,这时也用一个数组,直接统计当前的贡献就行了

统计的时候有两个限制,一个是K的限制,一个是集合大小的限制

最后一起答案,每个节点的贡献为A串出现次数*B串出现次数*符合条件的字符串个数

#include <cstdio>
#include <algorithm>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 1e5 + 10; 					
int t[N << 1][60], len[N << 1], fa[N << 1], c[N << 1], k[N << 1], K;  	
int siz[N << 1], num[N << 1], sum[N << 1], cnt, last, n;               
char s[N], b[N];
ll ans;
 
void add(int c) 
{
	int p = last;
	int u = last = ++cnt;   
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;    
			rep(i, 0, 60) t[r][i] = t[q][i];
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
		}
	}
	siz[u] = 1;   					
}
 
int main()
{
	while(scanf("%d", &K) && K)
	{
		cnt = last = 1;
		memset(t, 0, sizeof t);
		scanf("%s%s", s + 1, b + 1);
		n = strlen(s + 1);
		_for(i, 1, n) add(s[i] - 'A');

		int temp = strlen(b + 1), u = 1, cur = 0;
		_for(i, 1, temp)
		{
			int c = b[i] - 'A';
			while(u && !t[u][c]) u = fa[u], cur = len[u];
			if(!u) u = 1, cur = 0;
			else u = t[u][c], cur++;
			num[fa[u]]++;
			sum[u] += max(min(cur - K + 1, cur - len[fa[u]]), 0);
		}
	
		ans = 0;
		_for(i, 1, cnt) c[len[i]]++;       
		_for(i, 1, cnt) c[i] += c[i - 1];
		_for(i, 1, cnt) k[c[len[i]]--] = i;
		for(int i = cnt; i >= 1; i--)
		{
			int x = k[i];
			siz[fa[x]] += siz[x];
			num[fa[x]] += num[x];
			ans += 1LL * siz[x] * num[x] * max(min(len[x] - K + 1, len[x] - len[fa[x]]), 0);
			ans += 1LL * siz[x] * sum[x];
		}
		printf("%lld\n", ans);

		_for(i, 1, cnt) sum[i] = siz[i] = num[i] = k[i] = c[i] = 0;
	}
 
	return 0;
}

P3975 [TJOI2015]弦论(后缀自动机)

求第k大子串大小,加深了我对SAM的理解

求一个sum数组,表示在自动机上跑经过点u的子串的数量,也就是经过u能跑到多少个点,因为跑到一个点就是一个子串。

如果是本质不同的情况,那就是一个点初始化为1,然后求能达到的所有点的点权和。

如果是本质相同的情况,那个一个点就初始化为siz,也就是出现的次数。

然后注意1节点要初始化为0

区分不同的子串(自动机上),一个子串出现次数(parent tree上)

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 1e6 + 10; 						
int t[N][26], len[N], fa[N], c[N], k[N];  		
int siz[N], sum[N], cnt = 1, last = 1, n, K, op;              
char s[N];
 
void add(int c) 
{
	int p = last;
	int u = last = ++cnt;   
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;   
			_for(i, 0, 25) t[r][i] = t[q][i];        
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
		}
	}
	siz[u] = 1;   					   
}

void dfs(int u, int cur)
{
	if(cur <= 0) return;
	rep(i, 0, 26)
		if(t[u][i])
		{
			if(cur > sum[t[u][i]]) cur -= sum[t[u][i]];
			else
			{
				putchar(i + 'a');
				dfs(t[u][i], cur - siz[t[u][i]]);
				break;
			}
		}
}
 
int main()
{
	scanf("%s%d%d", s + 1, &op, &K);
	n = strlen(s + 1);
	_for(i, 1, n) add(s[i] - 'a');
 
	_for(i, 1, cnt) c[len[i]]++;              
	_for(i, 1, cnt) c[i] += c[i - 1];
	_for(i, 1, cnt) k[c[len[i]]--] = i;
	for(int i = cnt; i >= 1; i--) siz[fa[k[i]]] += siz[k[i]];
	_for(i, 1, cnt) 
	{
		if(!op) sum[i] = siz[i] = 1;
		else sum[i] = siz[i];
	}
	sum[1] = siz[1] = 0;                //1节点为0

	for(int i = cnt; i >= 1; i--)
		rep(j, 0, 26)
			if(t[k[i]][j])
				sum[k[i]] += sum[t[k[i]][j]];

	if(sum[1] < K) puts("-1");
	else dfs(1, K);
 
	return 0;
}

C. Cyclical Quest(后缀自动机)

给一个串S,每次询问一个串T,问S串中有多少个子串与T循环同构

如果不考虑循环同构,即T串在S串中出现了多少次,那么在SAM上预处理出每一个点的endpos集合个数,也就是出现了多少次,然后T串在上面跑,跑到某个点,某个点的这个值就是答案

考虑循环同构,循环同构常见的操作是把字符串加倍,这道题也一样

于是我们把字符串加倍,在S串上的SAM上跑。在这个加倍的串中,一个长度为T串长度的子串就是一个循环同构的串。我们边跑边统计匹配长度,当匹配长度大于等于T串长度时,我们就要统计答案

怎么统计答案呢,当前节点的endpos集合个数不一定当前的贡献,需要跳fail,也就是说让当前匹配的后缀更短,在大于等于T串的条件下尽可能短,这时endpos集合个数才是答案

同时有一个细节,可能T串的不同的循环同构出来的串是一样的,按照题意只能算一次,所以要用一个vis数组来记录一下

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 2e6 + 10; 						
int t[N][26], vis[N], len[N], fa[N], c[N], k[N];  		
int siz[N], sum[N], cnt = 1, last = 1, n;              
char s[N], a[N];
 
void add(int c) 
{
	int p = last;
	int u = last = ++cnt;   
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;   
			_for(i, 0, 25) t[r][i] = t[q][i];        
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
		}
	}
	siz[u] = 1;   					   
}

int main()
{
	scanf("%s", s + 1);
	n = strlen(s + 1);
	_for(i, 1, n) add(s[i] - 'a');
 
	_for(i, 1, cnt) c[len[i]]++;              
	_for(i, 1, cnt) c[i] += c[i - 1];
	_for(i, 1, cnt) k[c[len[i]]--] = i;
	for(int i = cnt; i >= 1; i--) siz[fa[k[i]]] += siz[k[i]];
	
	int T; scanf("%d", &T);
	_for(id, 1, T)
	{
		scanf("%s", a + 1);
		ll ans = 0;
		int lena = strlen(a + 1), u = 1, l = 0;
		_for(i, 1, lena * 2)
		{
			int c = i > lena ? a[i - lena] - 'a' : a[i] - 'a';
			while(u && !t[u][c]) u = fa[u], l = len[u];
			if(!u) u = 1, l = 0;
			else u = t[u][c], l++;

			if(l >= lena)
			{
				int v = u;
				while(!(len[v] >= lena && len[fa[v]] < lena)) v = fa[v];
				if(vis[v] != id)
				{
					vis[v] = id;
					ans += siz[v];
				}
			}
		}
		printf("%lld\n", ans);
	}

	return 0;
}

F. Forbidden Indices(后缀自动机)

模板题,稍微改一改

禁止的时候,初始化为0就行

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 4e5 + 10; 						
int t[N][26], vis[N], len[N], fa[N], c[N], k[N];  		
int siz[N], cnt = 1, last = 1, n;              
char s[N];
 
void add(int c, int op) 
{
	int p = last;
	int u = last = ++cnt;   
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;   
			_for(i, 0, 25) t[r][i] = t[q][i];        
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
		}
	}
	siz[u] = !op;   					   
}

int main()
{
	scanf("%d%s", &n, s + 1);
	_for(i, 1, n) 
	{
		int x; scanf("%1d", &x);
		add(s[i] - 'a', x);
	}

	_for(i, 1, cnt) c[len[i]]++;              
	_for(i, 1, cnt) c[i] += c[i - 1];
	_for(i, 1, cnt) k[c[len[i]]--] = i;
	for(int i = cnt; i >= 1; i--) siz[fa[k[i]]] += siz[k[i]];
	
	ll ans = 0;
	_for(i, 1, cnt) ans = max(ans, 1LL * len[i] * siz[i]);
	printf("%lld\n", ans);

	return 0;
}

周三

做一道字符串的题,推出了做法是需要求以一个数为最小值的区间,我只记得这个可以用单调栈来做,但具体实现忘记了,复习一下

P5788 【模板】单调栈

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 3e6 + 10;
int a[N], ans[N], s[N], top, n;

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &a[i]);
	for(int i = n; i >= 1; i--)
	{
		while(top && a[s[top]] <= a[i]) top--; //注意这里是while 不是 if  维持单调性
		ans[i] = top ? s[top] : 0;             //通过当前栈顶记录答案
		s[++top] = i;                          //将当前元素压入栈
	}
	_for(i, 1, n) printf("%d ", ans[i]);

	return 0;
}

​​​​​​[数据结构]——单调栈_lucky52529的博客-CSDN博客_单调递增栈

这篇博客不错

单调栈伪代码

分三步 保持单调性 统计答案 压入

	//添加结束标志,使得最后可以全部出栈 从而不会漏答案
	for()   //以某种顺序遍历数组
	{
		while(top && 不符合单调性) 
		{
			top--;
			统计答案
		}
		s.push(i);                //入栈 注意压入的是下标,比较的是下标对应的值
	}

P4248 [AHOI2013]差异

一.后缀数组+单调栈

我自己思考的时候是这种做法

可以发现len的部分是固定的,可以提前算出

关键是如何求任意两个后缀的lcp之和

后缀lcp那不就是后缀数组吗,所以求出height数组

转化为height数组在[2, n]上所有区间最小值之和

暴力是O(n^2)的,考虑另一种思考方式,即当前height[i]的贡献

即求出有多少区间是以当前height[i]为最小值的,求以当前元素为最值的区间就可以用单调栈

这里有重复的问题,我自己写的时候是跑两次单调栈,然后比较的时候一个取等号一个不取等号

看了别人代码发现可以跑一次就解决

入栈的时候,栈顶元素就是左侧区间,一个元素出栈的时候,当前要入栈的元素就是右侧区间,而且比较的时候写不写等号都可以。最后注意因为出栈要统计答案,所以栈里还有元素的话要全部出栈

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 5e5 + 10;
int sa[N], rak[N], tp[N], tax[N], height[N];
int st[N], l[N], r[N], n, m, top;
char s[N];
ll ans;
 
void Qsort()
{
	_for(i, 0, m) tax[i] = 0;
	_for(i, 1, n) tax[rak[i]]++;
	_for(i, 1, m) tax[i] += tax[i - 1];
	for(int i = n; i >= 1; i--) sa[tax[rak[tp[i]]]--] = tp[i];
}
 
void SuffixSort()
{
	m = 200;          
	_for(i, 1, n) rak[i] = s[i], tp[i] = i;
	Qsort();
	for(int w = 1, p = 0; p < n; m = p, w <<= 1)
	{
		p = 0;
		_for(i, 1, w) tp[++p] = n - i + 1;
		_for(i, 1, n) if(sa[i] > w) tp[++p] = sa[i] - w;
		Qsort(); swap(tp, rak);
		rak[sa[1]] = p = 1;
		_for(i, 2, n)
			rak[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++p;
	}
 
	int k = 0;
	_for(i, 1, n)
	{
		if(k) k--;
		int j = sa[rak[i] - 1];
		while(i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
		height[rak[i]] = k;
	}
}
 
int main()
{
	scanf("%s", s + 1);
	n = strlen(s + 1);
	SuffixSort();

	_for(i, 2, n)
	{
		while(top && height[st[top]] >= height[i]) r[st[top--]] = i;
		l[i] = top ? st[top] : 1;
		st[++top] = i;
	}
	while(top) r[st[top--]] = n + 1;

	ll ans = 1LL * (n + 1) * n * (n - 1) / 2, lcp = 0;
	_for(i, 2, n) lcp += 1LL * height[i] * (i - l[i]) * (r[i] - i);
	printf("%lld\n", ans - 2 * lcp);
 
	return 0;
}

二.后缀自动机

这题后缀自动机的思路真是太妙了

在parent tree上两个点的lca就是两个后缀的最长公共后缀

题目是lcp,所以可以先翻转一下变成lcs

然后,把树上一个点到它父亲的边权定义为len[x] - len[fa[x]]

这样就可以发现,题目式子所表达的含义就是一条路径的长度,妙啊

那么就转化为一个树,求所有路径之和

可以计算每跳边的贡献,边权乘以经过它的路径数

经过它的路径数就是siz[x] * (n - siz[x])

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 1e6 + 10; 					
int t[N][26], len[N], fa[N], c[N], k[N];  	
int siz[N], cnt = 1, last = 1, n;               
char s[N];
ll ans;
 
void add(int c) 
{
	int p = last;
	int u = last = ++cnt;   
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;    
			rep(i, 0, 26) t[r][i] = t[q][i];
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
		}
	}
	siz[u] = 1;   					
}
 
int main()
{
	scanf("%s", s + 1);
	n = strlen(s + 1);
	for(int i = n; i >= 1; i--) add(s[i] - 'a');

	_for(i, 1, cnt) c[len[i]]++;
	_for(i, 1, cnt) c[i] += c[i - 1];
	_for(i, 1, cnt) k[c[len[i]]--] = i;
	for(int i = cnt; i >= 1; i--)
	{
		int x = k[i];
		siz[fa[x]] += siz[x];
		ans += 1LL * (len[x] - len[fa[x]]) * siz[x] * (n - siz[x]);
	}
	printf("%lld\n", ans);
 
	return 0;
}

P5341 [TJOI2019]甲苯先生和大中锋的字符串(后缀自动机)

任意子串出现次数可以用SAM,处理出siz数组来求

然后差分一下就可以了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 2e5 + 10; 					
int t[N][26], len[N], fa[N], c[N], k[N], d[N], K;  	
int siz[N], cnt, last, n;               
char s[N];
 
void add(int c) 
{
	int p = last;
	int u = last = ++cnt;   
	len[u] = len[p] + 1;
	for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
	if(!p) fa[u] = 1;
	else
	{
		int q = t[p][c];
		if(len[q] == len[p] + 1) fa[u] = q;
		else
		{
			int r = ++cnt;    
			rep(i, 0, 26) t[r][i] = t[q][i];
			fa[r] = fa[q];
			len[r] = len[p] + 1;
			fa[q] = fa[u] = r;
			for(; p && t[p][c] == q; p = fa[p])
				t[p][c] = r;
		}
	}
	siz[u] = 1;   					
}
 
int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		cnt = last = 1;
		scanf("%s%d", s + 1, &K);
		n = strlen(s + 1);
		_for(i, 1, n) add(s[i] - 'a');

		_for(i, 1, cnt) c[len[i]]++;
		_for(i, 1, cnt) c[i] += c[i - 1];
		_for(i, 1, cnt) k[c[len[i]]--] = i;

		for(int i = cnt; i >= 1; i--)
		{
			int x = k[i];
			siz[fa[x]] += siz[x];
			if(siz[x] == K) d[len[fa[x]] + 1]++, d[len[x] + 1]--;
		}

		int mx = 0, ans;
		_for(i, 1, n - 1) d[i] += d[i - 1];
		_for(i, 1, n - 1)
			if(mx <= d[i]) 
			{
				mx = d[i];
				ans = i;
			}
		printf("%d\n", mx ? ans : -1);

		_for(i, 0, cnt) 
		{
			len[i] = fa[i] = c[i] = k[i] = siz[i] = d[i] = 0;
			rep(j, 0, 26) t[i][j] = 0;
		}
	}

	return 0;
}

poj 2976(分数规划)

做题遇到了分数规划这个知识点,学一学

分数规划就是有一种东西有a b两种权值

让你选一些,使得a权值和比b权值和最大

常用的方法是二分答案,然后推式子

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <functional>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 1e3 + 10;
int a[N], b[N], n, k;
double t[N];

bool check(double m)
{
	_for(i, 1, n) t[i] = a[i] - b[i] * m;
	nth_element(t + 1, t + n - k, t + n + 1, greater<double>());
	double sum = 0;
	_for(i, 1, n - k) sum += t[i];
	return sum >= 0;
}
 
int main()
{
	while(scanf("%d%d", &n, &k))
	{
		if(!n && !k) break;           //这样写保险 k有可能为0
		_for(i, 1, n) scanf("%d", &a[i]);
		_for(i, 1, n) scanf("%d", &b[i]);

		double l = 0, r = 1;
		while(l + 1e-4 < r)
		{
			double m = (l + r) / 2;
			if(check(m)) l = m;
			else r = m;
		}
		printf("%.f\n", l * 100);   //这样写可以四舍五入
	}

	return 0;
}

P4377 [USACO18OPEN] Talent Show G(分数规划+背包)

分数规划的题都是二分答案,只是判断的时候不同

这题判断用背包,这个背包有点不一样,因为是可以超W的,所以写的时候,如果超W了可以视为W,具体看代码

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 300;
const int M = 1e3 + 10;
int t[N], w[N], n, W;
double v[N], dp[M];

bool check(double m)
{
	_for(i, 1, n) v[i] = t[i] - w[i] * m;
	memset(dp, -0x3f, sizeof dp);
	dp[0] = 0;
	_for(i, 1, n)
		for(int j = W; j >= 0; j--)
			dp[j] = max(dp[j], dp[max(j - w[i], 0)] + v[i]);
	return dp[W] >= 0;
}
 
int main()
{
	scanf("%d%d", &n, &W);
	_for(i, 1, n) scanf("%d%d", &w[i], &t[i]);

	double l = 0, r = 1e6;
	while(l + 1e-5 < r)
	{
		double m = (l + r) / 2;
		if(check(m)) l = m;
		else r = m;
	}
	printf("%d\n", (int)(1000 * l));

	return 0;
}

珂朵莉树模板

学一学这个 应用条件

1.有区间赋值操作

2.数据随机 否则会被构造数据卡成暴力

本质上就是应用set,维护三元组(l, r, v)

因为有区间赋值操作导致区间个数小,保证复杂度

struct node
{
	int l, r;
	mutable int v;          //注意关键字mutable
	bool operator < (const node& rhs) const
	{
		return l < rhs.l;
	}
};
set<node> s;
int n;
 
auto split(int x)
{
    auto it = s.lower_bound({x, 0, 0});
    if (it != s.end() && it->l == x) return it;
    --it;
    int l = it->l, r = it->r, v = it->v;
    s.erase(it);
    s.insert({l, x - 1, v});
    return s.insert({x, r, v}).first;
}
 
void assign(int l, int r, int v)
{
	auto itr = split(r + 1), itl = split(l);   //注意先r后l
	s.erase(itl, itr);
	s.insert({l, r, v});
}
 
void add(int l, int r, int x)
{
	auto itr = split(r + 1), itl = split(l);
	for(auto it = itl; it != itr; it++) 
	{
		//做某种操作
		it->v += x;
	}
}

CF915E Physical Education Lessons(珂朵莉树)

这题用珂朵莉树直接秒了 

这题有区间赋值,用珂朵莉树,cf数据也没有卡这个

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
struct node
{
	int l, r;
	mutable int v;        
	bool operator < (const node& rhs) const
	{
		return l < rhs.l;
	}
};
set<node> s;
int n, q, sum;

auto split(int x)
{
    auto it = s.lower_bound({x, 0, 0});
    if (it != s.end() && it->l == x) return it;
    --it;
    int l = it->l, r = it->r, v = it->v;
    s.erase(it);
    s.insert({l, x - 1, v});
    return s.insert({x, r, v}).first;
}

void assign(int l, int r, int v)
{
	auto itr = split(r + 1), itl = split(l);  
	for(auto it = itl; it != itr; it++) sum -= (it->r - it->l + 1) * it->v;
	s.erase(itl, itr);
	s.insert({l, r, v});
	sum += (r - l + 1) * v;
}
 
int main()
{
	scanf("%d%d", &n, &q);
	s.insert({1, n, 1});
	sum = n;

	while(q--)
	{
		int l, r, k;
		scanf("%d%d%d", &l, &r, &k);
		if(k == 1) assign(l, r, 0);
		else assign(l, r, 1);
		printf("%d\n", sum);
	}

	return 0;
}

周四

C. Willem, Chtholly and Seniorious(珂朵莉树)

这题是珂朵莉树的起源

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
const int N = 1e5 + 10;
struct node
{
	int l, r;
	mutable ll v;        
	bool operator < (const node& rhs) const
	{
		return l < rhs.l;
	}
};
set<node> s;
int a[N], n, m;
ll seed, vmax;

auto split(int x)
{
    auto it = s.lower_bound({x, 0, 0});
    if (it != s.end() && it->l == x) return it;
    it--;
    int l = it->l, r = it->r; ll v = it->v;
    s.erase(it);
    s.insert({l, x - 1, v});
    return s.insert({x, r, v}).first;
}

void assign(int l, int r, ll x)
{
	auto itr = split(r + 1), itl = split(l);  
	s.erase(itl, itr);
	s.insert({l, r, x});
}

void add(int l, int r, ll x)
{
	auto itr = split(r + 1), itl = split(l);
	for(auto it = itl; it != itr; it++) it->v += x;
}

ll cal(int l, int r, ll k)
{
	auto itr = split(r + 1), itl = split(l);
	vector<pair<ll, int>> ve;
	for(auto it = itl; it != itr; it++) ve.push_back({it->v, it->r - it->l + 1});
	sort(ve.begin(), ve.end());
	for(auto x: ve)
	{
		if(x.second >= k) return x.first;
		else k -= x.second;
	}
	return 0;
}

ll binpow(ll a, ll b, ll mod)
{
	ll res = 1;
	a %= mod;              //注意这里  传进来的a可能非常大 
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}

ll sum(int l, int r, ll x, ll y)
{
	ll res = 0;
	auto itr = split(r + 1), itl = split(l);
	for(auto it = itl; it != itr; it++) 
		res = (res + binpow(it->v, x, y) * (it->r - it->l + 1) % y) % y;
	return res;	
}

ll rnd()
{
	ll ret = seed;
	seed = (seed * 7 + 13) % 1000000007;
	return ret;
}
 
int main()
{
	scanf("%d%d%lld%lld", &n, &m, &seed, &vmax);
	_for(i, 1, n) a[i] = (rnd() % vmax) + 1, s.insert({i, i, a[i]});
	_for(i, 1, m)
	{
		int op = (rnd() % 4) + 1;
		int l = (rnd() % n) + 1;
		int r = (rnd() % n) + 1;
		if(l > r) swap(l, r);

		ll x, y;
		if(op == 3) x = (rnd() % (r - l + 1)) + 1;
		else x = (rnd() % vmax) + 1;
		if(op == 4) y = (rnd() % vmax) + 1;

		if(op == 1) add(l, r, x);
		else if(op == 2) assign(l, r, x);
		else if(op == 3) printf("%lld\n", cal(l, r, x));
		else printf("%lld\n", sum(l, r, x, y));
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值