排列 题解
题意简述
给你 \(n\) 个点,第 \(i\) 个点有点权 \(v_i\)。
你需要构造一个排列,使得最终的满足条件的点的权值之和最大。
每个点都有一个前置区间 \([l_i, r_i]\),表示如果范围内的某个点排在 \(i\) 的前面了,那么 \(i\) 点的权值就要被累加进去。
应该如何排列,使得点权和才能最大。
\(n \leq 10^5\)。
题目分析
直接做并不好做,考虑放到图论上考虑。让 \(p \in [l_i, r_i]\) 向 \(i\) 连边,表示如果走到 \(p\) 就一定可以得到 \(i\) 的贡献。我们构造排列的过程,就是选取一个点,然后贪心地把这个点能到达的所有点依次加入到排列中,选取的那个点的权值不计,而后来加入的点有贡献。又由于最终每个点都在排列中,所以我们不妨反向考虑使那些不能产生贡献的点的权值之和最小。
这个有向图并没什么性质,我们不妨考虑用 tarjan 缩成一个 DAG。发现在 DAG 上,不能产生贡献的点都在入度为 \(0\) 的分量中,并且每一个这样的分量必须选取一个点作为用来“启动”的点。我们不妨在缩点的时候记一个最小值,那么答案就是所有入度为 \(0\) 的分量的最小值之和。
等一下,如果朴素建边时间复杂度会爆炸,这也很好解决,使用线段树优化建图即可。
时间复杂度:\(\Theta(n \log n)\)。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
int n, ans, val[100010];
struct Graph {
struct node {
int to, nxt;
} edge[100010 * 40];
int tot = 1, head[100010 << 2];
void add(int u, int v) {
edge[++tot] = {v, head[u]};
head[u] = tot;
}
inline node & operator [] (const int x) {
return edge[x];
}
} xym;
int whr[100010 << 2], tot;
#define lson (idx << 1 )
#define rson (idx << 1 | 1)
void build(int idx, int l, int r) {
if (l == r) {
whr[idx] = l;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
whr[idx] = ++tot;
xym.add(whr[lson], whr[idx]);
xym.add(whr[rson], whr[idx]);
}
void add(int idx, int trl, int trr, int l, int r, int u) {
if (trl > r || trr < l) return;
if (l <= trl && trr <= r) return xym.add(whr[idx], u);
int mid = (trl + trr) >> 1;
add(lson, trl, mid, l, r, u);
add(rson, mid + 1, trr, l, r, u);
}
int dfn[100010 << 2], low[100010 << 2], timer;
int stack[100010 << 2], top;
bool ins[100010 << 2];
vector<int> scc[100010 << 2];
int scc_cnt, sccno[100010 << 2];
int mi[100010 << 2];
bool vis[100010 << 2];
void tarjan(int now) {
dfn[now] = low[now] = ++timer;
stack[++top] = now, ins[now] = true;
for (int i = xym.head[now]; i; i = xym[i].nxt) {
int to = xym[i].to;
if (!dfn[to]) tarjan(to), low[now] = min(low[now], low[to]);
else if (ins[to]) low[now] = min(low[now], dfn[to]);
}
if (dfn[now] == low[now]) {
++scc_cnt, mi[scc_cnt] = 0x3f3f3f3f;
do {
int u = stack[top--];
ins[u] = false;
sccno[u] = scc_cnt;
scc[scc_cnt].push_back(u);
if (u <= n)
mi[scc_cnt] = min(mi[scc_cnt], val[u]);
} while (stack[top + 1] != now);
}
}
signed main() {
#ifndef XuYueming
freopen("permutation.in", "r", stdin);
freopen("permutation.out", "w", stdout);
#endif
scanf("%d", &n);
tot = n, build(1, 1, n);
for (int i = 1, L, R; i <= n; ++i) {
scanf("%d%d%d", &L, &R, &val[i]);
ans += val[i];
add(1, 1, n, L, R, i);
}
for (int i = 1; i <= tot; ++i)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= scc_cnt; ++i) {
for (auto u: scc[i]) {
for (int j = xym.head[u]; j; j = xym[j].nxt) {
int v = xym[j].to;
if (sccno[u] == sccno[v]) continue;
vis[sccno[v]] = true;
}
}
}
for (int i = 1; i <= scc_cnt; ++i) {
if (!vis[i] && mi[i] != 0x3f3f3f3f) {
ans -= mi[i];
}
}
printf("%d", ans);
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18397602。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。