周一
后缀自动机好难理解啊,看了一上午,感觉理解了一半吧……边做题边加深理解吧
这两篇博客讲的不错,下面写很多是这两篇博客的内容
史上最通俗的后缀自动机详解 - 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;
}