8
模板题 刚看完题解学了波 【朱-刘算法】 发发自己的见解
最小树形图,就是在【有向】带权图中指定一个特殊的点root,求一棵【以root为根】的有向生成树,并且树中所有边的总权值最小。
咋看之下是不是感觉跟最小生成树一样 = =
首先我们知道 【树】的根节点没有入边 出边可以无限多
而其他节点出边也可以无限多 但【入边只有一条】
既然要所有点都在树内 我们就贪心地选取【所有入点u的边中权值最小的】
如果此时能形成一棵树 那无疑就是最优解了
可以直接直觉认为 当前选的n-1条边中【没有边成环就能形成树】
如果有环 我们就进行缩点 【把在同一个环内的点看成一个点 同时更新指向环中任意一点的边权】
看着好像跟这道题一点关系都扯不上 那么废话不多说 上别人题解里的图
【1】 以1号节点为根 先贪心选一波边 发现成环
【2】 将2/3/4节点缩成一点 同时更新指向它的边的权值
可以看到1->4的边权是4 如果我们要选这条边 那么就可以不选3->4 因为【入边只有一条】
又因为3->4的边权已经计入答案了 所以这条边的边权可以变成4-2=2
选的话就又2+2+2变成2+2+4
其他同理 可以自己推一下加深印象
重复【1】【2】直到不成环
下面给出代码+注释
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e2+50;
const int maxm=1e4+50;
const int inf=0x3f3f3f3f;
int n,m,r;
ll ans;
inline int read() {
int a=0;char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
return a;
}
struct edge {int u,v,w;}e[maxm];
int cnt,fa[maxn],id[maxn],top[maxn],min[maxn];
//cnt当前图环的数量
//id[u]代表u节点在第id[u]个环中
//top[u]代表u所在链的代表元素 类似并查集
//min[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
inline int getans() {
while(1) {
for(register int i=1;i<=n;++i) id[i]=top[i]=0,min[i]=inf;
for(register int i=1;i<=m;++i)
if(e[i].u!=e[i].v&&e[i].w<min[e[i].v])
//不是自环 并且边权比选定的还小
fa[e[i].v]=e[i].u,min[e[i].v]=e[i].w;
int u=min[r]=0;
for(register int i=1;i<=n;++i) {
if(min[i]==inf) return 0; //存在一个不可以连接的点
ans+=min[i];
for(u=i;u!=r&&top[u]!=i&&!id[u];u=fa[u]) top[u]=i;
//找到包含不在环中的点最多的链 打上标记
if(u!=r&&!id[u]) { //这时候还满足条件说明vis[u]==i 即成环
id[u]=++cnt;
for(int v=fa[u];v!=u;v=fa[v]) id[v]=cnt;
}
}if(!cnt) return 1; //没环就是找到答案了
for(register int i=1;i<=n;++i) if(!id[i]) id[i]=++cnt;
//i节点不存在当前树中 就给他自己成一个环
for(register int i=1;i<=m;++i) {
int last=min[e[i].v];
//last等于当前连进v点的边的最小权值
if((e[i].u=id[e[i].u])!=(e[i].v=id[e[i].v])) e[i].w-=last;
//当前边的两个端点不在同一个环内
}n=cnt;r=id[r];cnt=0;
//缩完点后 当前点数就为环数 根节点就是根节点所在的环
}
}
int main() {
n=read();m=read();r=read();
for(register int i=0;i<m;e[++i]=(edge){read(),read(),read()});
if(getans()) printf("%lld",ans);
else printf("-1");
return 0;
}