「NOIP模拟」通讯【tarjan缩点】【似乎要拓扑?但是好麻烦啊】

【问题描述】

“这一切都是命运石之门的选择。”
试图研制时间机器的机关SERN截获了中二科学家伦太郎发往过去的一条短信,并由此得知了伦太郎制作出了电话微波炉(仮)。
为了掌握时间机器的技术,SERN总部必须尽快将这个消息通过地下秘密通讯网络,传达到所有分部。
SERN共有N个部门(总部编号为0),通讯网络有M条单向通讯线路,每条线路有一个固定的通讯花费Ci
为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向另一个与它有线路的部门传递(可能存在多条通信线路)。我们定义总费用为所有部门传递消息的费用和
幸运的是,如果两个部门可以直接或间接地相互传递消息(即能按照上述方法将信息由X传递到Y,同时能由Y传递到X),我们就可以忽略它们之间的花费。
由于资金问题(预算都花在粒子对撞机上了),SERN总部的工程师希望知道达到目标的最小花费是多少。

【输入格式】

多组数据,文件以2个0结尾。
每组数据第一行,一个整数N,表示有N个包括总部的部门(从0开始编号)。然后是一个整数M,表示有M条单向通讯线路。
接下来M行,每行三个整数,Xi,Yi,Ci,表示第i条线路从Xi连向Yi,花费Ci。

【输出格式】

每组数据一行,一个整数表示达到目标的最小花费

题目中说道可以相互到达的点不计入花费,即环上的点不考虑,所以缩点。

然后就直接有向图上dp求路线最小值之和。

似乎写了拓扑就不需要递归了?

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define db double
#define sg string
#define ll long long
#define mem(x,y) memset(x,y,sizeof(x))
#define rel(i,x,y) for(ll i=(x);i<(y);i++)
#define rep(i,x,y) for(ll i=(x);i<=(y);i++)
#define red(i,x,y) for(ll i=(x);i>=(y);i--)
#define res(i,x) for(ll i=head[x];i;i=nxt[i])
using namespace std;

const ll N=5e4+5;
const ll M=1e5+5;
const ll Inf=1e18;
const ll Mod=1e9+7;
const db Eps=1e-10;

ll top,scc,tim,dis[N],vis[N],size[N],stack[N];
ll n,m,f[N],dfn[N],low[N],bel[N];

struct nnode {
	ll num,dis;
	bool operator < (const nnode &a) const {
		return a.dis<dis;
	}
};

struct node {
	ll cnt,to[M],edge[M],nxt[M],head[N];

	void init() {
		cnt=0;mem(head,0);
	}

	void ins(ll x,ll y,ll z) {
		to[++cnt]=y;edge[cnt]=z;nxt[cnt]=head[x];head[x]=cnt;
	}
	
	void dp(ll s) {
		res(i,s) {
			ll y=to[i],z=edge[i];
			if(z<f[y]) f[y]=z,dp(y);
		}
	}
	
	void tarjan(ll x) {
		dfn[x]=low[x]=++tim;vis[x]=1;stack[++top]=x;
	
		for(ll i=head[x];i;i=nxt[i]) {
			ll y=to[i];
			if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
			else if(vis[y]) low[x]=min(low[x],dfn[y]);
		}
		
		if(dfn[x]!=low[x]) return ;
	
		scc++;
	
		while(true) {
			ll y=stack[top--];
			vis[y]=0;bel[y]=scc;
			if(x==y) return ;
		}
	}
}G1,G2;

char buf[1<<20],*p1,*p2;
#define getchar (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
inline ll read() {
	ll x=0;char ch=getchar;bool f=0;
	while(ch>'9'||ch<'0'){if(ch=='-')f=1;ch=getchar;}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar;}
	return f?-x:x;
}

void init() {
	G1.init(),G2.init();
	scc=top=tim=0;
	mem(bel,0);mem(dfn,0);
	mem(low,0);mem(vis,0);mem(stack,0);
}

void File() {
	freopen("message.in","r",stdin);
	freopen("message.out","w",stdout);
}

int main() {
//	File();
	
	while(true) {
		n=read(),m=read();
		
		if(n==0&&m==0) return 0;
		
		init();
		
		rep(i,1,m) {
			ll x=read()+1,y=read()+1,z=read();G1.ins(x,y,z);
		}
		
		rep(i,1,n) if(!dfn[i]) G1.tarjan(i);
		
		rep(i,1,n) for(ll j=G1.head[i];j;j=G1.nxt[j]) {
			ll y=G1.to[j],z=G1.edge[j];
			if(bel[i]!=bel[y]) G2.ins(bel[i],bel[y],z);
		} 
		
		rep(i,1,scc) f[i]=Inf;f[bel[1]]=0;

		G2.dp(bel[1]);
		
		ll ans=0;
		
		rep(i,1,scc) ans+=f[i];
		
		printf("%lld\n",ans);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值