专栏文章
题解:P13823 「Diligent-OI R2 C」所谓伊人
P13823题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mio613rp
- 此快照首次捕获于
- 2025/12/02 13:56 3 个月前
- 此快照最后确认于
- 2025/12/02 13:56 3 个月前
前置知识:多源 bfs,连通块。
题目描述
给定一张 个点, 条边的有向图,点从 编号。图中每个点 有点权 。注意可能有重边自环。
如果点 出发存在路径到达点 ,则你可以将 的点权交换。
对于每个点 ,输出使 点权最大化的最少交换次数。请注意,每个回答是独立的,即都应该从初始给定的图开始交换。
思路
因为若存在 的路径,则可将 的点权交换。同理若存在 的路径,也可以交换 的点权。
引理 1
若将所有有向边转成无向边后,图会被分成若干个连通块。对于每个连通块上的点,都存在最大权值 ,则该连通块上的所有点均可通过若干步将权值转化为 ,且 就是该连通块上的点能转化出来的最大权值。
大家自己思考便可得到,这里就不证明了。
现在考虑在每个连通块里面 bfs,求出最少交换次数。
那么这个路径长度怎么求呢?
引理 2
显然同方向路径长度不加,不同方向路径长度加 。
这里举个简单例子来方便大家理解。
有 个点 ,权值也是 ,节点编号和权值相等,如下图。
因为转成无向图后是连通的,所以每个点的权值都可以转化为 。- 节点 的权值最大,需要经过 此交换。
- 节点 可以和节点 的权值交换,需要一步。
- 节点 和节点 不能一次到达,所以要先交换 和 的权值,再交换 和 的权值,需要两次。
- 同理节点 也需要两次。
- 节点 和节点 可达,所以先交换 ,再交换 ,需要 次。
我们发现 到 的路径和 到 的路径方向相反,所以节点 需要的次数比节点 多 。而 到 的路径和 到 的路径方向相同,所以节点 的次数等于节点 的次数,不用加 。
我们可以建正图和反图,方便我们反向 bfs。
可以建立一个数组
dis[N][2]( 表示正边, 表示反边),最后 的答案即为 。代码
CPP#include <bits/stdc++.h>
using namespace std;
const int N = 500010, M = 1000010;
int n, m, k;
int p[N];
int head[N], e[M], ne[M], idx, rhead[N]; // 存边
int dis[N][2];
bool vis[N];
vector<int> v[N]; // 存储连通块
queue<pair<int, int> > q;
void add(int *h, int a, int b) {
e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}
// 遍历连通块
void dfs(int x) {
vis[x] = 1, v[k].push_back(x);
for (int i = head[x]; i; i = ne[i]) { // 正边
int j = e[i];
if (!vis[j]) dfs(j);
}
for (int i = rhead[x]; i; i = ne[i]) { // 反边
int j = e[i];
if (!vis[j]) dfs(j);
}
}
// 拓展所有同方向的点
void f(int x, int *h, int d,int p) {
for (int i = h[x]; i; i = ne[i]) {
int j = e[i];
if (dis[j][p] == 0x3f3f3f3f) {
dis[j][p] = d;
q.push({j, p});
f(j, h, d,p);
}
}
}
// 多源最短路
void bfs(int x) {
int maxx = 0;
for (int i : v[x]) maxx = max(maxx, p[i]); // 求最大权值
for (int i : v[x])
if (p[i] == maxx) {
dis[i][0] = dis[i][1] = 0;
q.push({i, -1}); // 起点是-1
}
while (q.size()) {
auto [u, p] = q.front();
q.pop();
if (p == 0) { // 正边
f(u, head, dis[u][p], 0); // 同向不变
f(u, rhead, dis[u][p] + 1, 1); // 不同向+1
} else if (p == 1) { // 反边
f(u, head, dis[u][p] + 1, 0);
f(u, rhead, dis[u][p], 1);
} else { // 起点必+1
f(u, head, dis[u][0] + 1, 0);
f(u, rhead, dis[u][0] + 1, 1);
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &p[i]);
while (m--) {
int u, v;
scanf("%d%d", &u, &v);
if (u == v) continue;
add(head, u, v), add(rhead, v, u); // 正向建边,反向建边
}
for (int i = 1; i <= n; i++)
if (!vis[i])
k++, dfs(i); // 处理连通块
memset(dis, 0x3f, sizeof dis); // 赋极大值
for (int i = 1; i <= k; i++) bfs(i);
for (int i = 1; i <= n; i++) printf("%d ", min(dis[i][0], dis[i][1])); // 取最小值
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...