题解洛谷 P2659【美丽的序列】

看到题解区基本全是单调栈,我来一个不同的思路——用并查集来贪心。

这是一个比较经典的 trick,常用来批量处理一条链/一棵树上路径的最大或最小值问题,大致的思路是按顺序枚举每一条边,并查集维护连通块来算贡献。

具体到本题,我们可以把数列抽象成一条 \(n+1\)\(n\) 边的链,\(i\)\(i+1\) 之间的边权为 \(a_i\)。同时初始化一个有 \(n+1\) 个点的并查集,需要记录每个集合的大小。

考虑一条边做出贡献的条件是什么,就是这条边是路径上边权最小的。因此我们将所有边按边权从大到小排序,然后按顺序枚举每一条边,根据它连接的两个连通块的大小和它的边权计算贡献,之后把这条边加到图中(也就是合并它连接的两个连通块)。

由于我们按边权降序枚举,显然在加入一条边时,过这条边的每条路径的最小边权都由这条边贡献。

时间复杂度为 \(\mathcal{O}(n\log n)\),开 O2 可过。

//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 2e6+5;

int n, a[N];
vector<tuple<int, int, int> > e;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Dsu {
	int fa[N], sz[N];
	void init(int x) {rep(i, 1, x) fa[i] = i, sz[i] = 1;}
	int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
	bool merge(int x, int y) {
		int u = find(x), v = find(y);
		if(u == v) return 0;
		if(sz[u] < sz[v]) swap(u, v);
		fa[v] = u;
		sz[u] += sz[v];
		sz[v] = 0;
		return 1;
	}
}dsu;

int main() {
	scanf("%d", &n);
	rep(i, 1, n) scanf("%d", &a[i]);
	rep(i, 1, n) e.push_back(make_tuple(i, i+1, a[i]));
	dsu.init(n+1);
	sort(e.begin(), e.end(), [](const auto& a, const auto& b) {
		return get<2>(a) > get<2>(b);
	});
	ll ans = 0;
	for(auto i : e) {
		int u = get<0>(i), v = get<1>(i), w = get<2>(i);
		chkmax(ans, 1LL * (dsu.sz[dsu.find(u)] + dsu.sz[dsu.find(v)] - 1) * w);
		dsu.merge(u, v);
	}
	printf("%lld\n", ans);
	return 0;
}

这个 trick 还是很有用的,我遇到过好多次,感兴趣的话可以做一下这些题:

posted @ 2022-03-29 15:06  rui_er  阅读(42)  评论(0编辑  收藏  举报