题解洛谷 P2659【美丽的序列】
看到题解区基本全是单调栈,我来一个不同的思路——用并查集来贪心。
这是一个比较经典的 trick,常用来批量处理一条链/一棵树上路径的最大或最小值问题,大致的思路是按顺序枚举每一条边,并查集维护连通块来算贡献。
具体到本题,我们可以把数列抽象成一条 点 边的链, 与 之间的边权为 。同时初始化一个有 个点的并查集,需要记录每个集合的大小。
考虑一条边做出贡献的条件是什么,就是这条边是路径上边权最小的。因此我们将所有边按边权从大到小排序,然后按顺序枚举每一条边,根据它连接的两个连通块的大小和它的边权计算贡献,之后把这条边加到图中(也就是合并它连接的两个连通块)。
由于我们按边权降序枚举,显然在加入一条边时,过这条边的每条路径的最小边权都由这条边贡献。
时间复杂度为 ,开 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 还是很有用的,我遇到过好多次,感兴趣的话可以做一下这些题:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通