题解 P6033 【Ryoku 的探索】
P6033 [Ryoku 的探索] 题解
声明:转载本文或文中内容(包括图片)请注明出处,否则本人保留依法追究其责任的权力。
吐槽
为啥我觉得很简单但是很多人说很难?
其实出题人这一题没有出的太难,因为只需要找一个结论就好。
正文
既然上面已经提到是结论题了,那么结论是什么呢?
题目说到,这是一张
我们分析一下这句话,什么情况下一个端点会走过?就是走到环上了。画张图就明白了:
(一棵环为
由上图可见,无论从哪个点进入环,环上总会有一条边不能经过(因为总会有一个端点被访问过),所以我们现在先考虑环上的点会忽略哪些边。
step1:对环上节点的处理
以上面那幅图为例,假设从
同时,你会发现如果我们从
因此对于环上的节点,我们可以把所有边的
如果你觉得比较懵,我们综合代码来理解:
if(vis[x]) {
一些内容;
return true;
} vis[x] = 1;
上面这段代码的意思时:执行遍历的时候,我们用
刚刚提到了,存环需要回溯的时候实现,我们不妨把
先定义一些数组、变量:
int head[N], ver[M], Next[M], e[M], p[M];
// 边表
bool vis[N]; int End, Ep, Ee;
vector<int> ring;
long long ans[N];
// ans 答案 ring 存环用 vis 标记访问
// End 环的终止 Ep 终边的美观度 Ee 终边的长度
// 对于 End Ep Ee 下面还会解释
下面为整个找环过程的完整代码:
bool vis[N]; int End, Ep, Ee;
vector<int> ring;
long long ans[N];
bool dfs(int x, int fa, int fp, int fe) {
if(vis[x]) {
End = x, Ep = fp, Ee = fe;
return true;
}
vis[x] = 1;
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if(y == fa) continue;
if(dfs(y, x, p[i], e[i])) {
if(x != End)
ans[x] -= fp > p[i] ? e[i] : fe;
else ans[x] -= p[i] > Ep ? Ee : e[i];
ring.push_back(x);
return x != End;
}
}
return false;
}
请你观察回溯部分,如果回溯时这个点还在环上,即不是我们标记的环的
继续观察第一个
那么
(还有不理解可以看代码或者私信我)
对于
step2:对环外节点的处理
再次观察上面的结论,我们只解决了环上节点,那么环外节点呢?其实这就很简单了。比如对于节点
不信?不懂?那好,我们举个例子。假设从
那么就可以对环上节点一一遍历,将环看为根,给其子树一一赋值。
void dfs(int x) {
vis[x] = 1;
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if(vis[y]) continue;
ans[y] = ans[x];
dfs(y);
}
}
以上就是赋值。赋值前把
代码
(我知道你们只看这个)
(但是我的解释都在正文中,这是一份裸的代码)
#include<bits/stdc++.h>
#define reg register
using namespace std;
const int N = 1e6 + 10;
const int M = 2e6 + 10;
int head[N], ver[M], Next[M], e[M], p[M];
void add(reg int x, reg int y, reg int edge, reg int pi) {
static int cnt = 0;
ver[++cnt] = y, Next[cnt] = head[x], head[x] = cnt;
e[cnt] = edge, p[cnt] = pi;
ver[++cnt] = x, Next[cnt] = head[y], head[y] = cnt;
e[cnt] = edge, p[cnt] = pi;
}
bool vis[N]; int End, Ep, Ee;
vector<int> ring;
long long ans[N];
bool dfs(reg int x, reg int fa, reg int fp, reg int fe) {
if(vis[x]) {
End = x, Ep = fp, Ee = fe;
return true;
}
vis[x] = 1;
for(reg int i = head[x]; i; i = Next[i]) {
reg int y = ver[i];
if(y == fa) continue;
if(dfs(y, x, p[i], e[i])) {
if(x != End)
ans[x] -= fp > p[i] ? e[i] : fe;
else ans[x] -= p[i] > Ep ? Ee : e[i];
// 减去不会经过的边的答案
ring.push_back(x);
return x != End;
}
}
return false;
}
void dfs(reg int x) { // 给子树赋值
vis[x] = 1;
for(reg int i = head[x]; i; i = Next[i]) {
reg int y = ver[i];
if(vis[y]) continue;
ans[y] = ans[x];
dfs(y);
}
}
int read() {
static char c;
static int x;
while(!isdigit(c = getchar()));
x = c ^ 48;
while(isdigit(c = getchar()))
x = (x << 3) + (x << 1) + (c ^ 48);
return x;
}
int main() {
reg int n = read();
reg long long sum = 0;
for(reg int i = 1, x, y, e, p; i <= n; i++) {
x = read(), y = read();
e = read(), p = read();
add(x, y, e, p);
sum += e; // 把所有边权叠加
}
dfs(1, 0, 0, 0); memset(vis, 0, sizeof vis);
for(reg int i = 0; i < (int)ring.size(); i++)
vis[ring[i]] = 1, ans[ring[i]] += sum;
// 计算环上的答案
for(reg int i = 0; i < (int)ring.size(); i++)
dfs(ring[i]);
for(reg int i = 1; i <= n; i++)
printf("%lld\n", ans[i]);
return 0;
}
因为
后记
感谢大家阅读了我的题解,同时也再次重申:若要转载,请注明出处。谢谢大家的观看和理解。
如果题解中有存在错误或者不理解的地方,请私信我(洛谷)。
(不知道可不可以无耻地要个赞)