洛谷 P3627 [APIO2009] 抢掠计划 Kosaraju
·

题目分析:
初看题,可能想到用BFS遍历图,然后将每次取完钱之后的结果累计,遇到酒吧就更新答案值。但是,本题为有向有环图,直接使用BFS必然会导致死循环,而标记每个走过的节点,又不能维护后来的值优于先来的值的情况。于是,需要先对强联通分量并入一个集合中进行缩点处理(即让多个可以相互到达的点看作一个整体),之后就可以开心的用BFS处理了。
关于缩点处理,本文采用的是Kosaraju算法,即用两遍DFS预处理图:
第一次 DFS,选取任意顶点作为起点,遍历所有未访问过的顶点,并在之后给顶点从小到大编号。
第二次 DFS,对于反向后的图,以标号最大的顶点作为起点开始 DFS。这样遍历到的顶点集合就是一个强连通分量。然后对于所有未访问过的结点,选取标号最大的,重复上述过程,直到所有的点都被访问过。
最后,用BFS遍历缩点后的图(因为可以相互到达的点都缩成了一个集合,所以剩下的图必然是单向的),然后如果当前值大于先前的值,就访问该节点,即可。
代码实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n, m, a, u, v, fa[500010] = {}, s, p, cnt = 0, sum = 0, now;
bool vis[500010] = {};
vector<int>vt;
struct node {
int a = 0;
int mx = 0;
vector<int>nx;
vector<int>ny;
bool s = false;
}e[500010];
inline void dfs1(int u) {
vis[u] = true;
for (vector<int>::iterator p = e[u].nx.begin(); p != e[u].nx.end();p++) {
if (!vis[*p]) {//如果未访问,则访问
dfs1(*p);
}
}
vt.emplace_back(u);
}
inline void dfs2(int u) {将一个最大联通分量中的点都加入下标最小的点中
fa[u] = cnt;//
if (u != cnt) {//不为本身则更新状态
e[cnt].a += e[u].a;
e[cnt].s = (e[cnt].s || e[u].s);
for (vector<int>::iterator p = e[u].nx.begin(); p != e[u].nx.end(); p++) {
e[cnt].nx.emplace_back(*p);
}
}
for (vector<int>::iterator p = e[u].ny.begin(); p != e[u].ny.end(); p++) {//在反图中遍历
if (!fa[*p])dfs2(*p);
}
}
queue<int>q;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> u >> v;
e[u].nx.emplace_back(v);//正向图
e[v].ny.emplace_back(u);//反向图,用于寻找最大联通分量
}
for (int i = 1; i <= n; i++) {
cin >> e[i].a;
}
cin >> s >> p;
while (p--) {
cin >> u;
e[u].s = true;
}
for (int i = 1; i <= n; i++) {
if(!vis[i])dfs1(i);//第一次DFS,遍历图,编号
}
for (int i = n - 1; i >= 0; i--) {
if (!fa[vt[i]]) {
cnt = vt[i];将一个最大联通分量中的点都加入下标最小的点中
dfs2(vt[i]);
}
}
q.push(fa[s]);
while (!q.empty()) {//BFS求解
now = q.front();
q.pop();
if (e[now].s)sum = max(sum, e[now].mx + e[now].a);//碰到酒吧,则更新答案值
for (vector<int>::iterator p = e[now].nx.begin(); p != e[now].nx.end(); p++) {
if (fa[*p] != now && e[fa[*p]].mx < e[now].mx + e[now].a) {//不在一个最大联通分量重且当前的钱数大于之前访问到该节点的最大数,则重新遍历
e[fa[*p]].mx = e[now].mx + e[now].a;
q.push(fa[*p]);
}
}
}
cout << sum;
}
更多推荐



所有评论(0)