P6037 Ryoku 的探索
题目背景
Ryoku 对自己所处的世界充满了好奇,她希望能够在她「死」之前尽可能能多地探索世界。
这一天,Ryoku 得到了一张这个世界的地图,她十分高兴。然而,Ryoku 并不知道自己所处的位置到底在哪里,她也不知道她会什么时候死去。她想要知道如何才能尽可能多的探索这个世界。
题目描述
Ryoku 所处的世界可以抽象成一个有 nn 个点, nn 条边的带权无向连通图 GG。每条边有美观度和长度。
Ryoku 会使用这样一个策略探索世界:在每个点寻找一个端点她未走过的边中美观度最高的走,如果没有边走,就沿着她前往这个点的边返回,类似于图的深度优先遍历。
探索的一个方案的长度是这个方案所经过的所有边长度的和(返回时经过的长度不用计算)。
她想知道,对于每一个起点 s=1,2,\cdots,ns=1,2,⋯,n,她需要走过的长度是多少?
输入格式
输入包含 n + 1n+1 行,其中第一行包含一个整数 nn。
接下来 nn 行每行包含四个整数 u,v,w,pu,v,w,p,描述了一条连接 uu 和 vv,长度为 ww,美观度为 pp 的无向边。
输出格式
输出包含 nn 行,每行一个整数,第 ii 行为 s=is=i 时的答案。
输入输出样例
输入 #1复制
5
4 1 2 1
1 2 3 2
3 1 1 4
3 5 2 5
2 3 2 3
输出 #1复制
7
7
8
7
8
说明/提示
【样例 1 说明】
以下为输入输出样例 1 中的图: (边上红色数组为 pp,黑色为 ww)
若起点为 11,顺序为 1\to3\to5\to2\to41→3→5→2→4,长度之和为 77。
若起点为 22,顺序为 2\to3\to5\to1\to42→3→5→1→4,长度之和为 77。
若起点为 33,顺序为 3\to5\to1\to2\to43→5→1→2→4,长度之和为 88。
若起点为 44,顺序为 4\to1\to3\to5\to24→1→3→5→2,长度之和为 77。
若起点为 55,顺序为 5\to3\to1\to2\to45→3→1→2→4,长度之和为 88。
【数据规模与约定】
对于 40%40% 的数据,n\le 10^3n≤10 3 。
对于 100%100% 的数据,3 \le n \le 10^63≤n≤10 6
,1 \le u,v,p \le n1≤u,v,p≤n,0\le w\le 10^90≤w≤10 9 ,保证 pp 互不相同。
第一次写这个类型的题,开始dfs写只有40分,后来寻思着是不是最小生成树,想着想着自闭了,今天看题解是拓扑排序,想想好长时间没写图了,该练练了。
题目给了n个点n条边,这样的图一定存在一个环,这样的图称为基环树。要遍历基环树时需走n-1条边,有个边是不用走的,求答案时用总长度减去那条边即可。怎么求那条边呢?进入基环树中的环后,会从这个点沿着美度高的点向前遍历,所以最后与这个点相邻的环中的美度最低点就是要抛去的(样例图中模拟一遍就会发现,如从3进入,3->1->2,3与2相邻的边要抛弃)。
实现步骤:
1.拓扑排序求出基环树的环;
2.依次求出环中的点x需要抛弃的边;
3.与x可到达的非环中的点也要减去抛弃的边。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define maxn 2000010
const int inf=0x3f3f3f3f;
struct node{
int next,to,w,p;
}s[maxn];
int du[maxn],num=0,head[maxn],vis[maxn],ans[maxn];
void add(int x,int y,int w,int p){
s[++num].next=head[x];
s[num].to=y;
s[num].w=w;
s[num].p=p;
head[x]=num;
}
void dfs(int x,int w){
ans[x]=w;
for(int i=head[x];i;i=s[i].next){
if(vis[s[i].to]&&!ans[s[i].to]){//x可到达非环的点
dfs(s[i].to,w);
}
}
}
int main()
{
int n,i,j,m,x,y,w,p;
long long sum=0;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d %d %d %d",&x,&y,&w,&p);
add(x,y,w,p);
add(y,x,w,p);
du[x]++;du[y]++;
sum+=w;
}
queue<int>q;
for(i=1;i<=n;i++)//入读为一的点进入队列(边界点)
if(du[i]==1) q.push(i);
while(!q.empty()){//拓扑排序求环
int t=q.front();
q.pop();
vis[t]=1;//标记的非环中的点
for(i=head[t];i;i=s[i].next){
int u=s[i].to;
du[u]--;
if(du[u]==1) q.push(u);//环与边界点之间的点
}
}
for(i=1;i<=n;i++){
if(!vis[i]){
int mw,ms=inf;
for(j=head[i];j;j=s[j].next){//求与这个点相邻的环中的美度最低点
if(!vis[s[j].to]&&s[j].p<ms){
ms=s[j].p;
mw=s[j].w;
}
}
dfs(i,mw);
}
}
for(i=1;i<=n;i++)
printf("%lld\n",sum-ans[i]);
return 0;
}
拓扑排序求最小环
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=400010;
struct node{
int to,next;
}s[maxn];
int num,head[maxn],du[maxn],vis[maxn];
void add(int u,int v){
s[++num].next=head[u];
s[num].to=v;
head[u]=num;
}
int dfs(int x){
vis[x]=1;
for(int i=head[x];i;i=s[i].next){
if(!vis[s[i].to]){
return dfs(s[i].to)+1;
}
}
return 1;
}
int main()
{
int x,n,i,j,m;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&x);
add(i,x);
du[x]++;
}
queue<int>q;
for(i=1;i<=n;i++)
if(!du[i]) q.push(i);
while(!q.empty()){
int t=q.front();
q.pop();
vis[t]=1;
for(i=head[t];i;i=s[i].next){
int u=s[i].to;
du[u]--;;
if(!du[u]) q.push(u);
}
}
int ans=0x3f3f3f3f;
for(i=1;i<=n;i++){
if(!vis[i]){//求环的长度
ans=min(ans,dfs(i));
}
}
printf("%d",ans);
return 0;
}
并查集求最小环
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int inf=0x3f3f3f3f;
int a[200005],cont;
int find(int x){
cont++;
return x==a[x]?x:(find(a[x]));
}
int main()
{
int m,i,j,n,x,y,k,ans=inf;
scanf("%d",&n);
for(i=1;i<=n;i++)
a[i]=i;
for(i=1;i<=n;i++){
scanf("%d",&x);
cont=0;
if(find(x)==i)
ans=min(cont,ans);
else
a[i]=x;
}
printf("%d",ans);
return 0;
}