2019常州集训 8.8
最大子段和(sum)(DP)
显然我们枚举断点,然后两边找最大子段和。
如果暴力的话,复杂度是 \(O(n^2)\) 的,显然会 \(T\) 。所以我们最好采取类 \(DP\) 的思路,分别从前到后,从后到前,线性先各跑一遍最大子段和,在此期间顺便维护好 \(DP\) 数组,这样就可以做到预处理 \(O(n)\) ,查询 \(O(1)\) ,将复杂度压到 \(O(n)\) 了。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 100010;
int n, a[N];
LL maxv_front[N], maxv_back[N];
int main()
{
//input
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
//solve
LL ans = 0, maxv = 0;
for (int i = 1; i <= n; ++i) {
if (maxv + a[i] <= 0) maxv = 0;
else maxv += a[i];
maxv_front[i] = ans = max(ans, maxv);
}
ans = 0, maxv = 0;
for (int i = n; i >= 1; --i) {
if (maxv + a[i] <= 0) maxv = 0;
else maxv += a[i];
maxv_back[i] = ans = max(ans, maxv);
}
//output
ans = 0;
for (int i = 1; i < n; ++i)
ans = max(ans, maxv_front[i] + maxv_back[i+1]);
printf("%lld", ans);
return 0;
}
序列(sequence)(差分)
普通差分是给区间加上相同的值,但是这题就离谱,加的是一个等差数列。
注意到,等差数列差分后的数组是多个相同值,那么我们可以将差分数组再次差分,然后每次加的时候在这个二次差分数组上面进行操作。计算答案的时候,先从二次差分数组上面维护得原差分数组,然后再次维护,得到原数组(答案)。
#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
unsigned long long ans[N];
long long cf0[N], cf1[N], cf2[N];
int main()
{
int n, m, l, r;
long long s, e;
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%lld%lld", &l, &r, &s, &e);
long long dx = (e - s) / (r - l);
if (dx) cf2[l] += dx, cf2[r+1] -= dx,
cf0[l] += s - dx, cf0[r+1] -= e;
else cf0[l] += s, cf0[r+1] -= s;
}
for (int i = 1; i <= n; ++i)
cf1[i] = cf1[i-1] + cf2[i], cf0[i] += cf1[i];
unsigned long long output = 0;
for (int i = 1; i <= n; ++i)
ans[i] = ans[i-1] + cf0[i], output ^= ans[i];;
printf("%llu", output);
return 0;
}
合并果子(merge)(带权并查集)
这题据说可以 \(splay\) 硬写,但是并不是很会(溜了溜了
对于多次合并某一个集合,显然容易想到并查集,但是这题同时需要维护每个柠檬的榨汁值,确实离谱。
不难用并查集的思路想到,每次榨汁的时候,只在该连通块的父亲点进行操作即可,每次查询的时候看一下自己的榨汁值和父亲的榨汁值即可(按照路径压缩的思路,一个点要么自己就是连通块的父亲,要么其父亲恰好就是连通块父亲,整个树不超过两层)。
但是多次合并的时候,这个值传递显得比较繁琐,我们需要在 \(\text{find}\) 和 \(\text{merge}\) 上面做手脚。
\(\text{find}\) 不难理解,按照普通路径压缩的方式,不断加上父亲节点的榨汁值即可。
\(merge\) 比较离谱,我一直以为是在父节点上面操作,然后发现在子节点上面操作更容易(逃
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n, q, fa[N], s[N];
void init() {
for (int i = 1; i <= n; ++i) fa[i] = i;
}
int find(int x) {
if (x == fa[x]) return x;
int ttem = fa[x];
int tem = find(fa[x]);
if (tem != ttem)
s[x] += s[ttem];
return fa[x] = tem;
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x != y) s[x] -= s[y], fa[x] = y;
}
void work(int x, int y) {
s[find(x)] += y;
}
int main()
{
scanf("%d%d", &n, &q);
init();
//solve
for (int i = 1; i <= q; i++) {
int opt, x, y;
scanf("%d%d%d", &opt, &x, &y);
(opt == 1) ? merge(x, y) : work(x, y);
}
//output
for (int i = 1; i <= n; i++) {
int tem = find(i);
if (i == tem)
printf("%d ", s[i]);
else
printf("%d ", s[i] + s[tem]);
}
return 0;
}