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;
}
posted @ 2021-03-02 15:48  cyhforlight  阅读(94)  评论(0编辑  收藏  举报