图论5:Tarjan!塔尖!

小模板

强联通分量

luoguP3387【模板】缩点
有向图,强联通分量中的点两两可以互相到达。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=1e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,v[N],val[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[N],to[N];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
}

int sta[N],top,dfk,dfn[N],low[N],bl[N],tot;
void tarjan(int x) {
	sta[++top]=x;
	dfn[x]=low[x]=++dfk;
	for(int i=fir[x];i;i=nxt[i]) {
		if(!dfn[to[i]]) {
			tarjan(to[i]);
			low[x]=min(low[x],low[to[i]]);
		}
		else if(!bl[to[i]]) low[x]=min(low[x],dfn[to[i]]);
	}
	if(dfn[x]==low[x]) {
		++tot;
		while(top) {
			int y=sta[top--];
			bl[y]=tot;
			val[tot]+=v[y];
			if(y==x) break;
		}
	}
}

int in[N];
vector<int>vc[N];
queue<int>que;
void Add(int u,int v) {
	vc[u].push_back(v); in[v]++;
}

int dp[N];
void tpsort() {
	For(x,1,n) for(int i=fir[x];i;i=nxt[i]) 
		if(bl[x]!=bl[to[i]]) Add(bl[x],bl[to[i]]);
	For(x,1,tot) if(!in[x]) { dp[x]=val[x]; que.push(x); }
	while(!que.empty()) {
		int x=que.front();
		que.pop();
		int up=vc[x].size();
		For(i,0,up-1) {
			int y=vc[x][i];
			in[y]--;
			dp[y]=max(dp[y],dp[x]+val[y]);
			if(!in[y]) que.push(y);
		}
	}
	int ans=dp[1];
	For(i,1,tot) ans=max(ans,dp[i]);
	printf("%d\n",ans);
}

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n); read(m);
    For(i,1,n) read(v[i]);
    For(i,1,m) {
		int u,v;
		read(u); read(v);
		add(u,v);
	}
	For(i,1,n) if(!dfn[i]) tarjan(i);
	tpsort();
	Formylove;
}

割点

luoguP3388 【模板】割点(割顶)
割点:无向图,去掉这个点会增加联通块的数目。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=2e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,cut[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[N],to[N];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}

int dfk,dfn[N],low[N],rtson;
void tarjan(int x,int RT) {
	dfn[x]=low[x]=++dfk;
	for(int i=fir[x];i;i=nxt[i]) {
		if(!dfn[to[i]]) {
			tarjan(to[i],RT);
			low[x]=min(low[x],low[to[i]]);
			if(x!=RT&&low[to[i]]>=dfn[x]) cut[x]=1;
			else if(x==RT) rtson++;
		}
		else low[x]=min(low[x],dfn[to[i]]);
	}
	if(x==RT&&rtson>1) cut[x]=1;
}

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n); read(m);
    For(i,1,m) {
		int u,v;
		read(u); read(v);
		add(u,v);
	}
	For(i,1,n) if(!dfn[i]) { rtson=0; tarjan(i,i); }
	int ans=0;
	For(i,1,n) if(cut[i]) ans++;
	printf("%d\n",ans);
	For(i,1,n) if(cut[i]) printf("%d ",i); puts("");
	Formylove;
}

无向图,去掉这条边后联通块数目增加。
ZOJ - 2588 Burning Bridges
处理重边,记录下每个点的父亲边,且不用这条边更新我的dfn,最后若我的dfn=low,我的父亲边就是桥。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=2e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m,cut[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[N],to[N],id[N];
void add(int u,int v,int i) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; id[ecnt]=i;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; id[ecnt]=i;
}

int dfk,dfn[N],low[N],feg[N];
void tarjan(int x) {
	dfn[x]=low[x]=++dfk;
	for(int i=fir[x];i;i=nxt[i]) {
		if(!dfn[to[i]]) {
			feg[to[i]]=i;
			tarjan(to[i]);
			low[x]=min(low[x],low[to[i]]);
		}
		else if(id[i]!=id[feg[x]]) low[x]=min(low[x],dfn[to[i]]);
	}
	if(dfn[x]==low[x]) cut[id[feg[x]]]=1;
}

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(T);
    while(T--) {
	    read(n); read(m);
	    For(i,1,m) {
			int u,v;
			read(u); read(v);
			add(u,v,i);
		}
		For(i,1,n) if(!dfn[i]) tarjan(i);
		int ans=0;
		For(i,1,m) if(cut[i]) ans++;
		printf("%d\n",ans);
		For(i,1,m) if(cut[i]) {
			ans--;
			if(ans) printf("%d ",i);
			else printf("%d\n",i);
		}
		if(T) {
			puts("");
			ecnt=dfk=0;
			memset(fir,0,sizeof(fir));
			memset(cut,0,sizeof(cut));
			memset(dfn,0,sizeof(dfn));
		}
	}
	Formylove;
}

边双连通分量

对于一个无向图的子图,当删除其中任意一条边后,不改变图内点的连通性,这样的子图叫做边的双连通子图。而当子图的边数达到最大时,叫做边的双连通分量。

显然求出桥后把所有桥删掉剩下的就是边双连通分量了。把dfs到的点入栈,每次当我的父亲边是桥时在栈中的所有点是一个边双,全部弹出即可。

hihocoder#1184 : 连通性二·边的双连通分量
每个边双的编号为边双中点的最小标号,输出边双的数目和每个点所在边双的编号。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=2e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m,cut[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt=1,fir[N],nxt[N],to[N];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}

int dfk,dfn[N],low[N],sta[N],top,tot,bl[N];
void tarjan(int x,int fe) {
	sta[++top]=x;
	dfn[x]=low[x]=++dfk;
	for(int i=fir[x];i;i=nxt[i]) if((fe^1)!=i) {
		if(!dfn[to[i]]) {
			tarjan(to[i],i);
			low[x]=min(low[x],low[to[i]]);
		}
		else low[x]=min(low[x],dfn[to[i]]);
	}
	if(dfn[x]==low[x]) {
		tot++;
		int id=x;
		for(int i=top;i;i--) {
			int y=sta[i];
			id=min(id,y);
			if(y==x) break;
		}
		while(top) {
			int y=sta[top--];
			bl[y]=id;
			if(y==x) break;
		}
	}
}

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
	read(n); read(m);
	For(i,1,m) {
		int u,v;
		read(u); read(v);
		add(u,v);
	}
	For(i,1,n) if(!dfn[i]) tarjan(i,0);
	printf("%d\n",tot);
	For(i,1,n) printf("%d ",bl[i]); puts("");
	Formylove;
}

点双连通分量

对于一个无向图的子图,当删除其中任意一个点后,不改变图内点的连通性,这样的子图叫做点的双连通子图。而当子图的边数达到最大时,叫做点的双连通分量。

割点将图分为若干点双,每个割点可能属于多个点双,但每条边仅属于一个点双。把边入队,每次找到一个割点,当前栈里的边属于同一个点双,弹出即可。每条边仅入队一次。

hihocoder#1190 : 连通性·四
每个点双的编号为点双中边的最小标号,输出点双的数目和每条边所在点双的编号。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=2e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m,cut[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt=1,fir[N],nxt[N],to[N];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}

int dfk,dfn[N],low[N],sta[N],top,tot,bl[N],vis[N];
void tarjan(int x) {
	dfn[x]=low[x]=++dfk;
	for(int i=fir[x];i;i=nxt[i]) {
		if(vis[i]) continue;
		if(!dfn[to[i]]) {
			sta[++top]=i;
			vis[i]=vis[i^1]=1;
			tarjan(to[i]);
			low[x]=min(low[x],low[to[i]]);
			if(low[to[i]]>=dfn[x]) {
				tot++;
				int id=i/2;
				for(int j=top;j;j--) {
					int y=sta[j];
					id=min(id,y/2);
					if(y==i) break; 
				}
				while(top) {
					int y=sta[top--];
					bl[y/2]=id;
					if(y==i) break;
				}
			}
		}
		else {
			sta[++top]=i;
			vis[i]=vis[i^1]=1;
			low[x]=min(low[x],dfn[to[i]]);
		}
	}
}

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
	read(n); read(m);
	For(i,1,m) {
		int u,v;
		read(u); read(v);
		add(u,v);
	}
	For(i,1,n) if(!dfn[i]) tarjan(i);
	printf("%d\n",tot);
	For(i,1,m) printf("%d ",bl[i]); puts("");
	Formylove;
}

支配树

有向图,去掉x就无法从起点到y,x就是y的支配点。
一个非常详细的讲解:どこでもドア

CodeChef - GRAPHCNT

传送门
给定有向图,求无序点对(x,y)满足存在从1到x的路径和1到y的路径使两条路径仅有1这个交点。
即问1为根的支配树上lca为1的点对树。模板题。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=5e5+7;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,SZ[N];

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

vector<int>eg[N],vc[N];
int ecnt,fir[N],nxt[N],to[N];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	eg[v].push_back(u);
}

int fa[N],fg[N],sdom[N],idom[N];
int find(int x) {
	if(x==fa[x]) return x;
	int	nf=find(fa[x]);
	if(sdom[fg[fa[x]]]<sdom[fg[x]]) fg[x]=fg[fa[x]];
	fa[x]=nf; return nf;
}

int qry(int x) {
	find(x);
	return fg[x];
}

int dfk,dfn[N],p[N],tid[N];
void dfs(int x,int Fa) {
	fa[x]=fg[x]=x;
	p[x]=Fa;
	dfn[x]=++dfk;
	tid[dfk]=x;
	sdom[x]=dfn[x];
	for(int i=fir[x];i;i=nxt[i]) if(!dfn[to[i]]) 
		dfs(to[i],x);
}

void build() {
	//For(i,1,n) fa[i]=fg[i]=i;
	dfs(1,0);
	Rep(i,dfk,2) {
		int x=tid[i],F=p[x],up=eg[x].size();
		For(j,0,up-1) {
			int y=eg[x][j];
			if(dfn[y]) sdom[x]=min(sdom[x],sdom[qry(y)]);
		}
		vc[tid[sdom[x]]].push_back(x);
		fa[x]=F;
		up=vc[F].size();
		For(j,0,up-1) {
			int w=vc[F][j];
			int v=qry(w);
			idom[w]=(sdom[v]==sdom[w]?F:v);
		}
	}
	For(i,2,dfk) { int x=tid[i]; idom[x]=(idom[x]==tid[sdom[x]]?idom[x]:idom[idom[x]]); }
	For(i,2,dfk) { int x=tid[i]; sdom[x]=tid[sdom[x]]; }
}

int main() {
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
    read(n); read(m);
    For(i,1,m) {
    	int x,y;
    	read(x); read(y);
    	add(x,y);
    }
    build();
    LL ans=0;// (LL)n*(n-1)/2;
    Rep(i,dfk,1) {
    	int x=tid[i];
    	SZ[x]++;
    	SZ[idom[x]]+=SZ[x];
 		if(idom[x]==1) 
 			ans-=(LL)SZ[x]*(SZ[x]-1)/2;
 		else if(x==1)
 			ans+=(LL)SZ[x]*(SZ[x]-1)/2;
 	}   
 	printf("%lld\n",ans);
    Formylove;
}

3281: 小P的烦恼

传送门
从s到t的路径上的必经边为特殊边。
用两条长度为L的线去覆盖图上的边使未被覆盖的特殊边的长度最短(可以只盖到边的一部分)。
化边为点,支配树求必经边。dijkstra跑出最短路,问题转换成数轴上的覆盖,可以随便搞。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=400007;
typedef long long LL; 
typedef double db;
using namespace std;
int Test,n,m,s,t,L;
int a[N],f[N],sum[N];

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct Tree {
	int fa[N],fg[N],sdom[N],idom[N];
	int find(int x) {
		if(fa[x]==x) return x;
		int nf=find(fa[x]);
		if(sdom[fg[fa[x]]]<sdom[fg[x]]) fg[x]=fg[fa[x]];
		fa[x]=nf; return nf;	
	}

	int qry(int x) {
		find(x);
		return fg[x];
	}

	vector<int>vc[N],eg[N];
	int ecnt,fir[N],nxt[N],to[N];
	void add(int u,int v) {
		nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
		eg[v].push_back(u);
	}

	int p[N],dfk,tid[N],dfn[N];
	void dfs(int x,int Fa) {
		p[x]=Fa;
		dfn[x]=++dfk;
		tid[dfk]=x;
		sdom[x]=dfk;
		for(int i=fir[x];i;i=nxt[i]) if(!dfn[to[i]])
			dfs(to[i],x);
	}

	void build() {
		dfs(s,0);
		Rep(i,dfk,2) {
			int x=tid[i],F=p[x],up=eg[x].size();
			For(j,0,up-1) {
				int y=eg[x][j];
				if(dfn[y]) sdom[x]=min(sdom[x],sdom[qry(y)]);
			}
			vc[tid[sdom[x]]].push_back(x);
			fa[x]=F; up=vc[F].size();
			For(j,0,up-1) {
				int w=vc[F][j];
				int v=qry(w);
				idom[w]=(sdom[v]==sdom[w]?F:v);
			}
		}
		For(i,2,dfk) { int x=tid[i]; idom[x]=(idom[x]==tid[sdom[x]]?idom[x]:idom[idom[x]]); }
		For(i,2,dfk) { int x=tid[i]; sdom[x]=tid[sdom[x]]; }
	}
	
	void init() {
		ecnt=dfk=0;
		For(i,1,n+m) fir[i]=dfn[i]=sdom[i]=idom[i]=0,fa[i]=fg[i]=i,vc[i].clear(),eg[i].clear();
	}
}T;

struct node {
	int x,dis;
	friend bool operator <(const node&A,const node&B) {
		return A.dis>B.dis;
	}
};
priority_queue<node>que;

#define inf 1e9
struct Graph {
	int ecnt,fir[N],nxt[N],fr[N],to[N],val[N];
	void add(int u,int v,int w) {
		nxt[++ecnt]=fir[u]; fir[u]=ecnt; fr[ecnt]=u; to[ecnt]=v; val[ecnt]=w;
	}
	
	int dis[N];
	void dijkstra() {
		For(i,1,n) dis[i]=inf;
		dis[s]=0;
		que.push((node){s,0});
		while(!que.empty()) {
			node tp=que.top();
			que.pop();
			int x=tp.x;
			if(dis[x]!=tp.dis) continue;
			for(int i=fir[x];i;i=nxt[i]) {
				int y=to[i];
				if(dis[y]>dis[x]+val[i]) {
					dis[y]=dis[x]+val[i];
					que.push((node){y,dis[y]});
				}
			}
		}
	}
	
	void init() {
		ecnt=0;
		memset(fir,0,sizeof(fir));
	}
}G;

int main() {
	//freopen("3281.in","r",stdin);
	//freopen("3281.out","w",stdout);
  	read(Test);
  	while(Test--) {
  		read(n); read(m); 
  		read(s); read(t); 
  		s++; t++ ;read(L);
  		T.init(); G.init();
  		For(i,1,m) {
  			int u,v,w;
  			read(u); read(v); read(w);
  			u++; v++;
  			G.add(u,v,w);
  			T.add(u,n+i);
  			T.add(n+i,v);
  		}
  		T.build();
  		G.dijkstra();
  		if(G.dis[t]==inf) puts("-1");
  		else {
	  		a[0]=0; int x=t;
	  		while(x!=s) {
	  			x=T.idom[x];
	  			if(x>n) a[++a[0]]=x-n;
	  		}
	  		For(i,1,a[0]) if(i<a[0]-i+1) swap(a[i],a[a[0]-i+1]);
	  		int ans=0;
	  		For(i,1,a[0]) sum[i]=sum[i-1]+G.val[a[i]];
	  		For(i,1,a[0]) {
	  			f[i]=0;
	  			int l=1,r=i,rs=1,v=G.to[a[i]];
	  			while(l<=r) {
	  				int mid=((l+r)>>1);
	  				int u=G.to[a[mid]];
	  				if(G.dis[v]-G.dis[u]<L) rs=mid,r=mid-1;
	  				else l=mid+1;
	  			}
	  			int u=G.fr[a[rs]];
	  			f[i]=sum[i]-sum[rs-1];
	  			if(G.dis[v]-G.dis[u]>L) f[i]-=(G.dis[v]-G.dis[u]-L);
	  			ans=max(ans,f[rs-1]+f[i]);

	  			l=1,r=i,rs=1,v=G.to[a[i]];
	  			while(l<=r) {
	  				int mid=((l+r)>>1);
	  				int u=G.to[a[mid]];
	  				if(G.dis[v]-G.dis[u]<L*2) rs=mid,r=mid-1;
	  				else l=mid+1;
	  			}
	  			u=G.fr[a[rs]];
	  			int tp=sum[i]-sum[rs-1];
	  			if(G.dis[v]-G.dis[u]>L*2) tp-=(G.dis[v]-G.dis[u]-L*2);
	  			ans=max(ans,tp);
	  			
	  			f[i]=max(f[i],f[i-1]);
	  		}
	  		printf("%d\n",sum[a[0]]-ans);
  		}
  	}
    Formylove;
}

test2018.3.3:problem C

传送门
问1到n的最短路上的必经点。
怎么是支配树的裸题啊。跑最短路后把dis[u]+val(u,v)>dis[v]的边(u,v)删掉,求支配点即可。
还问为什么求割点不行,当年我真是傻得可爱。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=400007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct Tree {
	int fa[N],fg[N],sdom[N],idom[N];
	int find(int x) {
		if(fa[x]==x) return x;
		int nf=find(fa[x]);
		if(sdom[fg[fa[x]]]<sdom[fg[x]]) fg[x]=fg[fa[x]];
		fa[x]=nf; return nf;	
	}

	int qry(int x) {
		find(x);
		return fg[x];
	}

	vector<int>vc[N],eg[N];
	int ecnt,fir[N],nxt[N],to[N];
	void add(int u,int v) {
		nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
		eg[v].push_back(u);
	}

	int p[N],dfk,tid[N],dfn[N];
	void dfs(int x,int Fa) {
		p[x]=Fa;
		dfn[x]=++dfk;
		tid[dfk]=x;
		sdom[x]=dfk;
		for(int i=fir[x];i;i=nxt[i]) if(!dfn[to[i]])
			dfs(to[i],x);
	}

	void build() {
		dfs(1,0);
		Rep(i,dfk,2) {
			int x=tid[i],F=p[x],up=eg[x].size();
			For(j,0,up-1) {
				int y=eg[x][j];
				if(dfn[y]) sdom[x]=min(sdom[x],sdom[qry(y)]);
			}
			vc[tid[sdom[x]]].push_back(x);
			fa[x]=F; up=vc[F].size();
			For(j,0,up-1) {
				int w=vc[F][j];
				int v=qry(w);
				idom[w]=(sdom[v]==sdom[w]?F:v);
			}
		}
		For(i,2,dfk) { int x=tid[i]; idom[x]=(idom[x]==tid[sdom[x]]?idom[x]:idom[idom[x]]); }
		For(i,2,dfk) { int x=tid[i]; sdom[x]=tid[sdom[x]]; }
	}
	
	void init() {
		ecnt=dfk=0;
		For(i,1,n) fa[i]=fg[i]=i;
	}
}T;

struct node {
	int x,dis;
	friend bool operator <(const node&A,const node&B) {
		return A.dis>B.dis;
	}
};
priority_queue<node>que;

#define inf 1e9
struct Graph {
	int ecnt,fir[N],nxt[N],fr[N],to[N],val[N];
	void add(int u,int v,int w) {
		nxt[++ecnt]=fir[u]; fir[u]=ecnt; fr[ecnt]=u; to[ecnt]=v; val[ecnt]=w;
	}
	
	int dis[N];
	void dijkstra(int s,int t) {
		For(i,1,n) dis[i]=inf;
		dis[s]=0;
		que.push((node){s,0});
		while(!que.empty()) {
			node tp=que.top();
			que.pop();
			int x=tp.x;
			if(dis[x]!=tp.dis) continue;
			for(int i=fir[x];i;i=nxt[i]) {
				int y=to[i];
				if(dis[y]>dis[x]+val[i]) {
					dis[y]=dis[x]+val[i];
					que.push((node){y,dis[y]});
				}
			}
		}
	}
	
	void solve() {
		dijkstra(1,n);
		if(dis[n]>=inf) {
			puts("-1");
			return ;
		}
		T.init();
		For(i,1,ecnt) if(dis[fr[i]]+val[i]==dis[to[i]])
			T.add(fr[i],to[i]);
  		T.build();
  		int ans=1;
  		for(int x=n;x!=1;x=T.idom[x]) ans++;
  		printf("%d\n",ans);
  		
	}
}G;

int main() {
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
  	read(n); read(m);
  	For(i,1,m) {
  		int u,v,w;
  		read(u); read(v); read(w);
  		G.add(u,v,w);
  	}
  	G.solve();
    Formylove;
}

仙人掌和圆方树

如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌
图(cactus)。所谓简单回路就是指在图上不重复经过任何一个顶点的回路。

どこでもドア
仙人掌中每个环都是一个点双,给每个点双建一个方点代表这个点双,环上每一个点视为圆点,都向这个方点连边。特别的,两个点一条边也看成一个点双。圆点只会与圆点相连,方点只会与方点相连。

bzoj2125: 最短路

どこでもドア
给定仙人掌,多次询问两点间最短路。
建出圆方树,方点到父亲(方点的父亲视为这个环的代表点)的距离设为0,方点的儿子到方点的距离设为点到代表点的距离的最小值。每次询问求书上最短路并讨论lca是否是方点即可。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=20007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,q,tot;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

map<LL,LL>mp;
LL get(int x,int y) { return 100000LL*min(x,y)+max(x,y); }

struct Tree {
    LL R[N],H[N],val[N<<1],L[N],ML[N];
    int ecnt,fir[N],nxt[N<<1],to[N<<1];
    void add(int u,int v,LL w,LL ww) {
        nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w; ML[v]=ww;
    }
    
    int f[N][18];
    void dfs(int x,int fa) {
        f[x][0]=fa;
        R[x]=R[fa]+1;
        For(i,1,15) f[x][i]=f[f[x][i-1]][i-1];
        for(int i=fir[x];i;i=nxt[i]) {
            H[to[i]]=H[x]+val[i];
            dfs(to[i],x);
        }
    }
    
    LL get_dis(int x,int y) {
        if(x==y) return 0;
        if(R[x]<R[y]) swap(x,y);
        int a=x,b=y;
        Rep(i,15,0) if(R[f[x][i]]>=R[y])
            x=f[x][i];
        if(x==y) return H[a]-H[x];
        Rep(i,15,0) if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
        int z=f[x][0];
        if(f[x][0]<=n) return H[a]+H[b]-2LL*H[z];
        else return H[a]-H[x]+H[b]-H[y]+min(L[z]-abs(ML[x]-ML[y]),abs(ML[x]-ML[y]));
    }

}T;

struct Graph{
    int ecnt,fir[N],nxt[N<<1],to[N<<1];
    LL val[N<<1];
    void init()  { ecnt=1; tot=n; }
    
    void add(int u,int v,LL w) {
        nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
        nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; val[ecnt]=w;
    }
    
    int dfn[N],low[N],sta[N],top,dfs_clock;
    void tarjan(int x,int F) {
        dfn[x]=low[x]=++dfs_clock;
        for(int i=fir[x];i;i=nxt[i]) if((i^1)!=F) {
            if(!dfn[to[i]]) {
                sta[++top]=i;
                tarjan(to[i],i);
                if(low[to[i]]>=dfn[x]) {
                    T.add(x,++tot,0,0);
                    LL H=mp[get(x,to[sta[top]])],L=H;
                    for(int j=top;j;j--) {
                        H+=val[sta[j]];
                        if(sta[j]==i) break;
                    }
                    T.L[tot]=H;
                    while(top) {
                        int j=sta[top--];
                        T.add(tot,to[j],min(L,H-L),L);
                        L+=val[j];
                        if(j==i) break;
                    }
                }
                low[x]=min(low[x],low[to[i]]);
            }
            else low[x]=min(low[x],dfn[to[i]]);
        }
    }
}G;

//#define DEBUG
int main() {
#ifdef DEBUG
    freopen("1.in","r",stdin);
    //freopen(".out","w",stdout);
#endif
    read(n); read(m); read(q);
    G.init();
    For(i,1,m) {
        int u,v; LL w;
        read(u); read(v); read(w);
        mp[get(u,v)]=w;
        G.add(u,v,w);
    }
    G.tarjan(1,0);
    T.dfs(1,0);
    For(i,1,q) {
        int x,y;
        read(x); read(y);
        printf("%lld\n",T.get_dis(x,y));
    }
    return 0;
}

bzoj4316: 小C的独立集

どこでもドア
给定仙人掌,求最大独立集。

建出圆方树然后随便怎么dp,我是用f[x][0/1]表示圆点自己选不选,g[x][0/1][0/1]表示方点下面最左边和最右边的圆点选不选的答案。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=2*60007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,q,tot,ans,sum;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

map<LL,LL>mp;
LL get(int x,int y) { return 100000LL*min(x,y)+max(x,y); }

struct Tree {
    int ecnt,fir[N],nxt[N<<1],to[N<<1],vis[N];
    int g[N][2][2],f[N][2];
    
    void add(int u,int v) {
        nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
    }
    
    void dfs(int x) {
        int tp[2][2][2],o=0;
        if(x>n) memset(tp,0,sizeof(tp));
        else f[x][1]=1; 
        for(int i=fir[x];i;i=nxt[i]) {
            dfs(to[i]);
            if(x<=n) {
                int tp=0; For(j,0,1) For(k,0,1) tp=max(tp,g[to[i]][j][k]);
                f[x][0]+=tp;
                f[x][1]+=g[to[i]][0][0];
            }
            else {
                if(!vis[x]) {
                    tp[o][0][0]=f[to[i]][0];
                    tp[o][1][1]=max(f[to[i]][1],f[to[i]][0]); vis[x]=1;
                }
                else {
                    o^=1;
                    For(j,0,1) {
                        tp[o][j][1]=tp[o^1][j][0]+max(f[to[i]][1],f[to[i]][0]);
                        tp[o][j][0]=max(tp[o^1][j][0],tp[o^1][j][1])+f[to[i]][0];
                    }
                }
            }
        }
        if(x>n) {
            For(j,0,1) For(k,0,1) { g[x][j][k]=tp[o][j][k]; ans=max(ans,tp[o][j][k]); }        
        }
        else For(i,0,1) ans=max(ans,f[x][i]);
    }
}T;

struct Graph{
    int ecnt,fir[N],nxt[N<<1],to[N<<1];
    void init()  { ecnt=1; tot=n; }
    
    void add(int u,int v,LL w) {
        nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
        nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
    }
    
    int dfn[N],low[N],sta[N],top,dfs_clock;
    void tarjan(int x,int F) {
        dfn[x]=low[x]=++dfs_clock;
        for(int i=fir[x];i;i=nxt[i]) if((i^1)!=F) {
            if(!dfn[to[i]]) {
                sta[++top]=i;
                tarjan(to[i],i);
                if(low[to[i]]>=dfn[x]) {
                    T.add(x,++tot);
                    while(top) {
                        int j=sta[top--];
                        T.add(tot,to[j]);
                        if(j==i) break;
                    }
                }
                low[x]=min(low[x],low[to[i]]);
            }
            else low[x]=min(low[x],dfn[to[i]]);
        }
    }
}G;

//#define DEBUG
int main() {
#ifdef DEBUG
    freopen("1.in","r",stdin);
    //freopen(".out","w",stdout);
#endif
    read(n); read(m);
    G.init();
    For(i,1,m) {
        int u,v; LL w;
        read(u); read(v);
        G.add(u,v,w);
    }
    For(i,1,n) if(!G.dfn[i]) {
        G.dfs_clock=0;
        G.tarjan(1,0);
        ans=0; T.dfs(1); sum+=ans;
    }
    printf("%d\n",sum);
    return 0;
}

bzoj1023: [SHOI2008]cactus仙人掌图

どこでもドア
求仙人掌直径,圆方树上dp,单调队列优化。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=2e5+7;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,tot,ans;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct Tree {
    int ecnt,fir[N],nxt[N<<1],to[N<<1],que[N<<1],ql,qr,f[N],tp[N],H;
    
    void init() {
        ecnt=0;
        memset(fir,0,sizeof(fir));
        memset(f,0,sizeof(f));
    }
    
    void add(int u,int v) {
        nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
    }
    
    void dfs(int x) {
        int fi=0,se=0;
        for(int i=fir[x];i;i=nxt[i]) dfs(to[i]);
        if(x<=n) {
            for(int i=fir[x];i;i=nxt[i]) {
                se=max(se,f[to[i]]);
                if(se>fi) swap(fi,se);
            }
            ans=max(ans,fi+se); f[x]=fi;
        }
        else {
            ql=1; qr=0; H=1;
            for(int i=fir[x];i;i=nxt[i])tp[++H]=to[i];
            For(i,1,H) {
                tp[i+H]=tp[i];
                f[x]=max(f[x],f[tp[i]]+min(i-1,H-(i-1)));
            }
            For(i,1,H+H/2) {
                if(ql<=qr&&i-que[ql]>H/2) ql++;
                if(ql<=qr) ans=max(ans,i-que[ql]+f[tp[que[ql]]]+f[tp[i]]);
                while(ql<=qr&&f[tp[que[qr]]]<=f[tp[i]]) qr--;
                que[++qr]=i;
            }
        }
    }
}T;

struct Graph{
    int ecnt,fir[N],nxt[N<<1],to[N<<1];
    int dfn[N],low[N],sta[N],top,dfs_clock;
    void init()  { 
        ecnt=1; tot=n; dfs_clock=0;
        memset(dfn,0,sizeof(dfn));
        memset(fir,0,sizeof(fir));
    }
    
    void add(int u,int v) {
        nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
        nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
    }
    
    void tarjan(int x,int F) {
        dfn[x]=low[x]=++dfs_clock;
        for(int i=fir[x];i;i=nxt[i]) if((i^1)!=F) {
            if(!dfn[to[i]]) {
                sta[++top]=i;
                tarjan(to[i],i);
                if(low[to[i]]>=dfn[x]) {
                    T.add(x,++tot);
                    while(top) {
                        int j=sta[top--];
                        T.add(tot,to[j]);
                        if(j==i) break;
                    }
                }
                low[x]=min(low[x],low[to[i]]);
            }
            else low[x]=min(low[x],dfn[to[i]]);
        }
    }
}G;

//#define DEBUG
int main() {
#ifdef DEBUG
    freopen("1.in","r",stdin);
    //freopen(".out","w",stdout);
#endif
    while(scanf("%d%d",&n,&m)==2) {
        G.init(); T.init();
        For(i,1,m) {
            int k,pr,x; read(k); read(pr);
            For(i,1,k-1) {
                read(x); G.add(pr,x); pr=x;
            }
        }
        G.tarjan(1,0);
        T.dfs(1);
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用并查集解决图论-桥问题的C++代码: ```c++ #include <iostream> #include <vector> using namespace std; const int MAXN = 1e5 + 5; int n, m, cnt, ans; int fa[MAXN], low[MAXN], dfn[MAXN]; vector<int> vec[MAXN]; int find(int x) { if (fa[x] == x) return x; return fa[x] = find(fa[x]); } void Union(int u, int v) { int x = find(u); int y = find(v); if (x != y) fa[x] = y; } void tarjan(int u, int pre) { low[u] = dfn[u] = ++cnt; for (int i = 0; i < vec[u].size(); i++) { int v = vec[u][i]; if (!dfn[v]) { tarjan(v, u); low[u] = min(low[u], low[v]); if (low[v] > dfn[u]) { ans++; Union(u, v); } } else if (v != pre) { low[u] = min(low[u], dfn[v]); } } } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) fa[i] = i; for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; vec[u].push_back(v); vec[v].push_back(u); } tarjan(1, 0); cout << ans << endl; return 0; } ``` 其中,`find`函数是并查集中的查找函数,`Union`函数是合并两个集合的函数。 在`tarjan`函数中,`low[u]`表示u能够到达的最小dfn值,`dfn[u]`表示u的dfs序,`pre`表示u的父亲节点。当`v`是`u`的子节点时,如果`v`还没有被访问过,就递归调用`tarjan`函数,并更新`low[u]`的值。如果`low[v] > dfn[u]`,则说明(u, v)是桥,合并u和v所在的集合。如果`v`已经访问过,并且v不是u的父亲节点,就更新`low[u]`的值。 最后输出ans即可,ans表示桥的数量。 注意:此代码实现并不是最优解,仅作为参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值