【模板】图论

图论

  • Johnson 全源最短路

  • k 短路

A*

可持久化可并堆

// LG2483 【模板】k 短路 / [SDOI2010] 魔法猪学院
const int N = 5e4+5, M = 2e5+5;
int n,m;
double e;
struct Graph {
	int mm,head[N],to[M],nxt[M];
	double w[M];
	void adde(int x,int y,double z)
		{ to[++mm] = y, w[mm] = z, nxt[mm] = head[x], head[x] = mm; }
} G,rG;

int ans,pre[N],dfn[N];
bool vis[N];
double dis[N];
struct Node { int id; double dis; };
bool operator < (const Node &x,const Node &y) { return x.dis > y.dis; }
priority_queue<Node> pq;

void dij() {
	mem(dis,127,n);
	pq.push(Node{n, dis[n]=0 });
	while( !pq.empty() ) {
		int u = pq.top().id; pq.pop();
		if( vis[u] ) continue; vis[u] = 1;
		for(int i = rG.head[u], v; v = rG.to[i], i; i = rG.nxt[i])
			if( dis[u]+rG.w[i] < dis[v] )
				pre[v] = i, pq.push(Node{v, dis[v]=dis[u]+rG.w[i] });
	}
}

#define ls(u) t[u].ch[0]
#define rs(u) t[u].ch[1]
int ind,rt[N];
struct Heap { int ch[2],to,dis; double val; } t[N*19];
int newnode(int to=0,double x=0) {
	t[++ind].to = to, t[ind].val = x;
	return ind;
}
int merge(int u,int v) {
	if( !u || !v ) return u | v;
	if( t[u].val > t[v].val ) swap(u,v);
	int w = newnode();
	t[w] = t[u], rs(w) = merge(rs(u),v);
	if( t[ls(w)].dis < t[rs(w)].dis ) swap(ls(w),rs(w));
	t[w].dis = t[rs(w)].dis+1;
	return w;
}

signed main() {
	scanf("%d%d%lf",&n,&m,&e);
	For(i,1,m) {
		int x,y; double z; scanf("%d%d%lf",&x,&y,&z);
		if( x == n ) { --i, --m; continue; }
		G.adde(x,y,z), rG.adde(y,x,z);
	}
	dij();
	For(i,1,n) dfn[i] = i;
	sort(dfn+1,dfn+n+1,[](const int &x,const int &y){return dis[x]<dis[y];});
	For(i,1,n) { int u = dfn[i];
		rt[u] = rt[G.to[pre[u]]];
		for(int j = G.head[u], v; v = G.to[j], j; j = G.nxt[j]) if( j != pre[u] )
			rt[u] = merge(rt[u],newnode(v,G.w[j]+dis[v]-dis[u]));
	}
	if( e < dis[1] ) return write(0), iocl();
	e -= dis[1], ++ans;
	if( rt[1] ) pq.push(Node{rt[1],t[rt[1]].val});
	while( !pq.empty() ) {
		Node u = pq.top(); pq.pop();
		if( e < dis[1]+u.dis ) break;
		e -= dis[1]+u.dis, ++ans;
		int v = ls(u.id);
		if( v ) pq.push(Node{v,u.dis-t[u.id].val+t[v].val});
		v = rs(u.id);
		if( v ) pq.push(Node{v,u.dis-t[u.id].val+t[v].val});
		v = rt[t[u.id].to];
		if( v ) pq.push(Node{v,u.dis+t[v].val});
	}
	write(ans);
	return iocl();
}
  • SCC 缩点
int nn,dfn[N],low[N],id[N];
Vi scc[N],ee[N];
void tj(int u) {
	static int t,s[N];
	dfn[u] = low[u] = ++dfn[0], s[++t] = u, id[u] = -1;
	for(auto [v,w] : e[u])
		if( !dfn[v] ) tj(v), ckmin(low[u], low[v]);
		else if( !~id[v] ) ckmin(low[u], dfn[v]);
	if( dfn[u] == low[u] ) {
		++nn;
		do id[s[t]] = nn, scc[nn].pb(s[t]); while( u != s[t--] );
	}
}
signed main() {
	For(i,1,n) if( !dfn[i] ) tj(i);
	For(u,1,n) for(int v : e[u]) if( id[u] != id[v] ) ee[id[u]].pb(id[v]);
}
  • 点双
int n,nn,dfn[N],low[N],id[N];
bool cut[N];
Vi e[N];
struct DCC {
	Vi ver;
	vector<Pii> edg;
} G[N];
void tj(int rt,int u) {
	static Vi stk;
	dfn[u] = low[u] = ++dfn[0], stk.pb(u);
	int son = 0;
	for(int v : e[u]) {
		if( !dfn[v] ) {
			tj(rt,v), ckmin(low[u],low[v]);
			if( dfn[u] <= low[v] ) {
				if( u != rt || son++ ) cut[u] = 1;
				G[++nn].ver.pb(u), id[u] = nn;
				int t; do {
					t = stk.back(), stk.pop_back(), G[nn].ver.pb(t), id[t] = nn;
					for(int i : e[t]) if( id[i] == nn ) G[nn].edg.pb(t,i);
			    } while( t != v );
			}
		} else ckmin(low[u],dfn[v]);
	}
}
signed main() {
	For(i,1,n) if( !dfn[i] ) tj(i,i);
}
  • 圆方树

欧拉图

  • 有向图是欧拉图 非零度结点弱连通,入度和出度相等

  • 有向图是半欧拉图 非零度结点弱连通,恰有两个结点分别满足出度比入度大一、入度比出度大一

// 结点字典序最小的欧拉路径
int n,m,in[N],out[N];
Vi ans;
vector<Pii> e[N];
void adde(int id,int x,int y) { e[x].pb(y,id), ++out[x], ++in[y]; }
void dfs(int u) {
	while( sz(e[u]) ) {
		auto [v,id] = e[u].back(); e[u].pop_back();
		dfs(v), ans.pb(id);
	}
}
void MAIN() {
	For(i,1,n) sort(all(e[i]),greater<>());
	int s = 0;
	For(i,1,n) if( out[i] > in[i] ) { s = i; break; }
	if( !s ) For(i,1,n) if( out[i] ) { s = i; break; }
	dfs(s);
	if( sz(ans) != m ) ;
	else rFor(i,m-1,0) cout<<ans[i]<<" ";
}
  • 无向图是欧拉图 非零度结点连通,度数都为偶数

  • 无向图是半欧拉图 非零度结点连通,恰有两个奇度结点

二分图

  • 匈牙利算法

O(nm)

int n,vis[N],match[N];
Vi e[N];
int dfs(int u,int t=0) {
	for(int v : e[u]) if( vis[v] != vis[0] ) {
		vis[v] = vis[0];
		if( match[v] == t || (match[v] > t && dfs(match[v],t)) )
			return match[v] = u, v;
	}
	return 0;
}

signed main() {
	For(i,1,n) sort(all(e[i]));
	For(i,1,n) if(++vis[0]; !dfs(i) ) puts("NIE"), exit(0);
	puts("TAK");
	For(i,1,n) ++vis[0], write(dfs(i,i)); // 最小字典序
}
  • KM 算法

  • 网络流

Dinic O(nm)

网络流

  • 最大流/最小割

Dinic O(n2m)

// 流量 long long
namespace flow {
const int N = ;
int S,T,fn,head[N],dis[N];
struct Edge {
	int to,b; LL c;
	Edge(int to=0,int b=0,LL c=0):to(to),b(b),c(c){}
}; vector<Edge> e[N];
void init(int n=0) { S = n+1, T = fn = n+2; }
void addedge(int x,int y,LL z=1e18) { e[x].pb(y,sz(e[y]),z), e[y].pb(x,sz(e[x])-1,0); }
bool bfs() {
	static int l,r,q[N];
	mem(head,0,fn), mem(dis,0,fn);
	dis[S] = 1, q[l=r=1] = S;
	while( l <= r ) {
		int u = q[l++];
		for(auto [v,b,c] : e[u]) if( c && !dis[v] )
			{ dis[v] = dis[u]+1, q[++r] = v; if( v == T ) return 1; }
	}
	return 0;
}
LL dfs(int u,LL in) {
	if( u == T ) return in;
	LL out = 0;
	for(int &i = head[u]; i < sz(e[u]); ++i)
		if(auto &[v,b,c] = e[u][i]; c && dis[u]+1 == dis[v] ) {
			if( LL f = dfs(v,min(c,in-out)) ) {
				c -= f, e[v][b].c += f;
				if( (out+=f) == in ) return in;
			} else dis[v] = 0;
		}
	return out;
}
LL dinic() { LL mf = 0; while( bfs() ) mf += dfs(S,1e18); return mf; }
} using flow::fn; using flow::S; using flow::T; using flow::addedge;
  • 最小费用最大流

SPFA+Dinic O(nmf)

// 流量 int,费用 long long
namespace flow {
const int N = ; const LL infl = 0x3f3f3f3f3f3f3f3f;
int S,T,fn,mf,mc,head[N],dep[N]; LL dis[N];
bitset<N> vis;
struct Edge {
	int to,b,c; LL w;
	Edge(int to=0,int b=0,int c=0,LL w=0):to(to),b(b),c(c),w(w){}
}; vector<Edge> e[N];
void init(int n=0) { S = n+1, T = fn = n+2; }
void addedge(int x,int y,int z=1e9,LL w=0)
	{ e[x].pb(y,sz(e[y]),z,w), e[y].pb(x,sz(e[x])-1,0,-w); }
bool spfa() {
	static queue<int> q;
	memset(head+1,0,sizeof(int)*fn), memset(dis+1,0x3f,sizeof(dis[0])*fn);
	dis[S] = 0, q.emplace(S);
	while( q.size() ) {
		int u = q.front(); q.pop(); vis[u] = 0;
		for(auto& [v,b,c,w] : e[u]) if( c && dis[u]+w < dis[v] ) {
			dis[v] = dis[u]+w, dep[v] = dep[u]+1;
			if( !vis[v] ) vis[v] = 1, q.emplace(v);
		}
	}
	return dis[T] < infl;
}
int dfs(int u,int f) {
	if( u == T ) return f;
	int use = 0;
	for(int &i = head[u]; i < sz(e[u]); ++i) {
		auto& [v,b,c,w] = e[u][i];
		if( c && dis[u]+w == dis[v] && dep[u]+1 == dep[v] ) {
			if( int g = dfs(v,min(c,f-use)) ) {
				c -= g, e[v][b].c += g;
				if( (use+=g) == f ) break;
			} else dis[v] = -1;
		}
	}
	return use;
}
void dinic() { while( spfa() ) { int f = dfs(S,1e9); mf += f, mc += f * dis[T]; } }
} using flow::fn; using flow::S; using flow::T; using flow::addedge;
  • 最大费用任意流

增广至最长路为负

  • 正费用最大流

总费用即将为负时停止流(注意最后一次增广可能无法获得增广路中全部流量)

  • 最大权闭合子图

原图中边流量为 infS 向正权点连流量为点权的边,负权点向 T 连流量为负点权的边。答案为 正点权和 - 最小割

最小割中与 S 在同一集合中的点为选择的点

上下界

  • 无源汇上下界可行流

l(u,v) 作为每条边的初始流,在新图中连边 (u,v,r(u,v)l(u,v)),考虑通过添加附加源汇 S,T、附加边调整至流量平衡

in[u]u 点初始流入流量,out[u] 为初始流出流量

  • in[u]=out[u]:流量平衡,不需要调整
  • in[u]>out[u]:相当于 u 在新图中凭空有 in[u]out[u] 流量,连边 (S,u,in[u]out[u])
  • in[u]<out[u]:类似的,连边 (u,T,out[u]in[u])

原图中流量平衡等价于新图中附加边流满,若新图最大流 =in[u]>out[u]in[u]out[u](即 S 的出边全部满流)则有解

  • 有源汇上下界可行流

原图的源汇为 s,t

原图只有 s,t 不满足流量平衡,通过连边 (t,s,[0,inf]) 可以转化为无源汇上下界可行流,st 的可行流流量为 ts 附加边的流量

  • 有源汇上下界最大流

先求出有源汇上下界可行流,此时已满足流量平衡,然后删去附加边,在残量网络上求 st 的最大流,答案即为 可行流 + 最大流
实现上求可行流后附加边仅有 (t,s,[0,inf]) 未满流,且该边反向边流量为 st 可行流大小,因此不需要手动删除附加边

// loj116 有源汇有上下界最大流
read(n,m,s,t);
flow::init(n);
For(i,1,m, u,v,l,r)
	read(u,v,l,r),
	dlt[u] -= l, dlt[v] += l, addedge(u,v,r-l);
For(i,1,n)
	if( dlt[i] > 0 ) addedge(S,i,dlt[i]), sum += dlt[i];
	else if( dlt[i] < 0 ) addedge(i,T,-dlt[i]);
addedge(t,s,inf);
if( flow::isap() != sum ) puts("please go home to sleep"), exit(0);
S = s, T = t;
write(flow::isap());
  • 有源汇上下界最小流

类似的,只需把可行流中不必要的流量退掉。在残量网络上求 ts 最大流,答案即为 可行流 - 最大流。
实现上求可行流后需记录可行流大小,手动删除 (t,s,[0,inf])

// loj117 有源汇有上下界最小流
cin>>n>>m>>s>>t;
flow::init(n);
For(i,1,m, u,v,l,r)
	cin>>u>>v>>l>>r,
	dlt[u] -= l, dlt[v] += l, addedge(u,v,r-l);
For(i,1,n)
	if( dlt[i] > 0 ) addedge(S,i,dlt[i]), sum += dlt[i];
	else if( dlt[i] < 0 ) addedge(i,T,-dlt[i]);
addedge(t,s,1e18);
if( flow::dinic() != sum ) { cout<<"please go home to sleep\n"; return; }
LL ans = flow::e[s].back().c;
flow::e[s].pop_back(), flow::e[t].pop_back(), S = t, T = s;
cout<<ans-flow::dinic();
  • 负圈最小费用最大流

有源汇上下界

namespace flow {
const int N = , inf = 0x3f3f3f3f; const LL infl = 0x3f3f3f3f3f3f3f3f;
int s,t,fn,S,T,mf,mc,dlt[N],head[N],dep[N]; LL dis[N];
bitset<N> vis;
struct Edge {
	int to,b,c; LL w;
	Edge(int to=0,int b=0,int c=0,LL w=0):to(to),b(b),c(c),w(w){}
}; vector<Edge> e[N];
void addedge(int x,int y,int z=1,LL w=0) {
	auto adde=[](int x,int y,int z,LL w)
		{ e[x].pb(y,sz(e[y]),z,w), e[y].pb(x,sz(e[x])-1,0,-w); };
	if( w >= 0 ) adde(x,y,z,w);
	else mc += z*w, adde(y,x,z,-w), dlt[x] -= z, dlt[y] += z;
}

void main() {
	For(i,1,fn)
		if( dlt[i] > 0 ) addedge(S,i,dlt[i]);
		else if( dlt[i] < 0 ) addedge(i,T,-dlt[i]);
	addedge(t,s,inf);
	dinic();
	mf = 0, S = s, T = t, dinic();
}
} using flow::fn; using flow::S; using flow::T; using flow::addedge;

  • Prufer 序列

n 个点的完全图的生成树 长度为 n2 值域为 [1,n] 的序列

// tree -> prufer
For(i,1,n-1) ++deg[fa[i]];
for(int i = 1, u = 1; i <= n-2; ++i, ++u) {
	while( deg[u] ) ++u; p[i] = fa[u];
	for(; i <= n-2 && !--deg[p[i]] && p[i] < u; ++i) p[i+1] = fa[p[i]];
}
// prufer -> tree
For(i,1,n-2) ++deg[p[i]]; p[n-1] = n;
for(int i = 1, u = 1; i < n; ++i, ++u) {
	while( deg[u] ) ++u; fa[u] = p[i];
	for(; i < n && !--deg[p[i]] && p[i] < u; ++i) fa[p[i]] = p[i+1];
}
  • ST 表 LCA
int n,rt,dfn[N],st[][N];
Vi e[N];
int amin(int u,int v) { return dfn[u]<dfn[v] ? u : v; }
void dfs(int u,int fa) {
	dfn[u] = ++dfn[0], st[0][dfn[0]] = fa;
	for(int v : e[u]) if( v != fa ) dfs(v,u);
}
int lca(int u,int v) {
	if( u == v ) return u;
	if( (u=dfn[u]) > (v=dfn[v]) ) swap(u,v);
	int k = __lg(v-u++);
	return amin(st[k][u],st[k][v-(1<<k)+1]);
}
signed main() {
	dfs(rt,0);
	For(i,1,) For(j,1,n-(1<<i)+1) st[i][j] = amin(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
  • tarjan LCA

  • 虚树

namespace T {
int dfn[N];
int lca(int x,int y) {}
}
namespace VT {
using namespace T;
bool is1[N];
Vi v2,e[N];
void adde(int x,int y) { e[x].pb(y), e[y].pb(x); }
void main(Vi &v1) {
	{ // build
		v1.resize(read()); for(int &i : v1) cin>>i, is1[i] = 1;
		auto cmp = [](int x,int y) { return dfn[x] < dfn[y]; };
		v2 = v1, sort(all(v2),cmp);
		Rep(i,1,sz(v1)) v2.pb(lca(v2[i-1],v2[i]));
		v2.pb(1), sort(all(v2),cmp), v2.erase(unique(all(v2)),v2.end());
		Rep(i,1,sz(v2)) adde(lca(v2[i-1],v2[i]),v2[i]);
	}
	{ // clear
		for(int i : v1) is1[i] = 0;
		for(int i : v2) e[i].clear();
	}
}
}

分治

  • 动态点分树
// LG3920 [WC2014] 紫荆花之恋
const int N = 1e5+5;
int n,val[N],dep[N],anc[N][47],dis[N][47],rt,siz[N],mxsz[N];
bool del[N];
Vi sub[N];
vector<Pii> e[N];
LL ans;

struct {
	int dlt;
	tree<Pii,null_type,less<>,rb_tree_tag,tree_order_statistics_node_update> t[N*2];
	void clr(int u) { t[u].clear(); }
	void ins(int u,int x) { t[u].insert({x,++dlt}); }
	int rk(int u,int x) { return t[u].order_of_key({x+1,0}); }
} bst;

#define v ei.fi
#define w ei.se
void getrt(int u,int fa) {
	siz[u] = mxsz[u] = 1;
	for(auto &ei : e[u]) if( !del[v] && v != fa )
		getrt(v,u), siz[u] += siz[v], ckmax(mxsz[u],siz[v]);
	ckmax(mxsz[u],siz[0]-siz[u]);
	if( mxsz[u] < mxsz[rt] ) rt = u;
}
void dfs(int u,int fa,int d) {
	sub[rt].pb(u), anc[u][++dep[u]] = rt, dis[u][dep[u]] = d;
	for(auto &ei : e[u]) if( !del[v] && v != fa ) dfs(v,u,d+w);
}
void dvd(int u,int size) {
	siz[rt=0] = size, getrt(u,0), del[u=rt] = 1;
	dfs(u,0,0);
	for(auto &ei : e[u]) if( !del[v] ) dvd(v,size-mxsz[v]);
}
#undef v
#undef w
void rbld(int u) {
	auto ver = sub[u]; int fa = anc[u][dep[u]-1];
	for(int i : ver) {
		while( anc[i][dep[i]] != fa ) --dep[i];
		del[i] = 0, sub[i].clear(), bst.clr(i), bst.clr(n+i);
	}
	mxsz[0] = sz(ver), dvd(u,mxsz[0]);
	for(int i : ver) for(int j = dep[i]; anc[i][j] != fa; --j)
		bst.ins(anc[i][j],dis[i][j]-val[i]), bst.ins(n+anc[i][j],dis[i][j-1]-val[i]);
}

signed main() {
	read(), io>>n;
	read(),read(), io>>val[1];
	anc[1][++dep[1]] = 1, dis[1][dep[1]] = 0,
	del[1] = 1, sub[1].pb(1), bst.ins(1,0-val[1]);
	io<<ans<<endl;
	For(u,2,n) {
		int fa,w; io>>fa>>w>>val[u], fa ^= ans%1000000000;
		e[fa].pb(u,w), e[u].pb(fa,w), dep[u] = dep[fa];
		For(i,1,dep[u]) anc[u][i] = anc[fa][i], dis[u][i] = dis[fa][i]+w;
		anc[u][++dep[u]] = u, dis[u][dep[u]] = 0,
		del[u] = 1, sub[u].pb(u), bst.ins(u,0-val[u]);
		rFor(i,dep[u]-1,1) {
			int x = anc[u][i], y = anc[u][i+1], d = dis[u][i];
			ans += bst.rk(x,val[u]-d) - bst.rk(n+y,val[u]-d),
			sub[x].pb(u), bst.ins(x,d-val[u]), bst.ins(n+y,d-val[u]);
		}
		Rep(i,1,dep[u]) if( sz(sub[anc[u][i]])*0.8 < sz(sub[anc[u][i+1]]) )
			{ rbld(anc[u][i]); break; }
		io<<ans<<endl;
	}
	return 0;
}
  • 边分树合并
// uoj400【CTSC2018】暴力写挂
const int N = 733335;
const LL infl = 0x3f3f3f3f3f3f3f3f;
int n;
LL ans=-infl;

struct Graph {
	int mm=1,head[N],nxt[N*2],to[N*2],w[N*2];
	void adde(int x,int y,int z) {
		nxt[++mm] = head[x], head[x] = mm, to[mm] = y, w[mm] = z,
		nxt[++mm] = head[y], head[y] = mm, to[mm] = x, w[mm] = z;
	}
};
#define eFor(g,u,i,v) for(int i = g.head[u], v; v = g.to[i], i; i = g.nxt[i])

#define ls(u) t[u].ch[0]
#define rs(u) t[u].ch[1]
struct {
	int ind,rt[N],lst[N];
	struct Node {
		int ch[2]; LL mx[2];
		Node() { memset(mx,0xcf,sizeof mx); }
	} t[N*22];
	void ins(int i,bool op,LL x) {
		int u = lst[i]; lst[i] = ++ind;
		t[u].ch[op] = ind, t[u].mx[op] = x;
	}
	int mrg(int u,int v,LL cs) {
		if( !u || !v ) return u | v;
		ckmax(ans,max(t[u].mx[0]+t[v].mx[1],t[u].mx[1]+t[v].mx[0])+cs),
		ckmax(t[u].mx[0],t[v].mx[0]), ckmax(t[u].mx[1],t[v].mx[1]);
		return ls(u) = mrg(ls(u),ls(v),cs), rs(u) = mrg(rs(u),rs(v),cs), u;
	}
} t;

namespace T1 {
int nn,rt,mxsz,siz[N];
LL dis[N];
Graph g0,g;
void bld(int u,int fa) {
	int x = 0;
	eFor(g0,u,i,v) if( v != fa ) {
		if( !x ) g.adde(u,v,g0.w[i]), x = u;
		else g.adde(x,++nn,0), g.adde(x=nn,v,g0.w[i]);
		dis[v] = dis[u]+g0.w[i], bld(v,u);
	}
}
void getrt(int u,int fa) {
	siz[u] = 1;
	eFor(g,u,i,v) if( v && v != fa ) {
		getrt(v,u), siz[u] += siz[v];
		if( ckmin(mxsz,max(siz[v],siz[0]-siz[v])) ) rt = i;
	}
}
void dfs(int u,int fa,LL d,bool op) {
	if( u <= n ) t.ins(u,op,d+dis[u]);
	eFor(g,u,i,v) if( v && v != fa ) dfs(v,u,d+g.w[i],op);
}
void dvd(int u,int size) {
	if( size == 1 ) return;
	siz[0] = mxsz = size, getrt(u,0);
	u = g.to[rt]; int v = g.to[rt^1]; g.to[rt] = g.to[rt^1] = 0;
//	cerr<<u<<' '<<v<<endl;
	dfs(u,0,0,0), dfs(v,0,g.w[rt],1);
	dvd(v,size-siz[u]), dvd(u,siz[u]);
}
void main() {
	nn = n, bld(1,0);
//	cerr<<"3 deg:\n";
//	For(u,1,nn) eFor(g,u,i,v) if( u < v ) cerr<<u<<' '<<v<<' '<<g.w[i]<<endl;
	For(i,1,n) t.rt[i] = t.lst[i] = ++t.ind;
	dvd(1,nn);
}
}

namespace T2 {
Graph g;
void dfs(int u,int fa,LL d) {
	ckmax(ans,2*(T1::dis[u]-d));
	eFor(g,u,i,v) if( v != fa )
		dfs(v,u,d+g.w[i]),
		t.rt[u] = t.mrg(t.rt[u],t.rt[v],-2*d);
}
}

signed main() {
	io>>n;
	Rep(i,1,n, x,y,z) io>>x>>y>>z, T1::g0.adde(x,y,z);
	Rep(i,1,n, x,y,z) io>>x>>y>>z, T2::g.adde(x,y,z);
	T1::main(), T2::dfs(1,0,0);
	io<<ans/2;
	cerr<<"t.ind = "<<t.ind<<endl;
	return 0;
}
posted @   ft61  阅读(43)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· 大模型工具KTransformer的安装
· [计算机/硬件/GPU] 显卡
点击右上角即可分享
微信分享提示