block3

back

生成树和最短路


· 并查集

//迭代式
int find(int u){ 
	int i=u,fu=u,j;
	while(fu!=fa[fu])fu=fa[fu];//查询 
	while(i!=fu){//路径压缩 
		j=fa[i];//先记录下i的父亲 
		fa[i]=fu;//i直接跟祖先相连 
		i=j;	//接下来修改i的父亲节点 
	}
	return fu;
}

· 最小生成树

时间复杂度:
kruskal是O(eloge),朴素prime是O(n^2),prime+优先队列是O(eloge)
稀疏图用kruskal,稠密图用prime+heap,空间足够的情况下都用prime+heap。

prime+优先队列

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e4+7;
const int M=5e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f;
	struct Edge{int v,Next;LL w;}e[M*2];
	struct node{
		int v;
		LL w;
		bool operator < (const node &o)const{
			return w>o.w;
		}
	};//优先队列类型 
	int n,m,head[N],tot,vis[N];
	LL dis[N];//不加dis[N]数组大数据会TLE
	void clear(){
		memset(head,0,sizeof(head));
		tot=0;
	}
	void add(int u,int v,LL w){e[++tot]=(Edge){v,head[u],w};head[u]=tot;}
	void addedge(int u,int v,LL w){add(u,v,w);add(v,u,w);}
	priority_queue<node>q;
	LL prime(int s=1){//返回最小生成树的总边权 或-1(非连通图) 
		for(int i=1;i<=n;i++)dis[i]=Inf;
		LL cost=0;
		node t{s,0};
		q.push(t);
		int Pointcnt=0;
		while(!q.empty()&&Pointcnt<=n){
			t=q.top();q.pop();
			if(vis[t.v])continue;
			vis[t.v]=1;
			cost+=t.w;
			Pointcnt++;
			for(int i=head[t.v];i;i=e[i].Next)
				if(!vis[e[i].v]&&e[i].w<dis[e[i].v]){
					q.push(node{e[i].v,e[i].w});
					dis[e[i].v]=e[i].w;
				}
		}
		if(Pointcnt==n)return cost;
		else return -1;//非连通图
	}
}G;
int main(){
	G.clear();
	cin>>G.n>>G.m;
	for(int i=1,u,v,w;i<=G.m;i++){
		scanf("%d%d%d",&u,&v,&w);
		G.addedge(u,v,(LL)w);
	}
	LL ans=G.prime();
	if(ans!=-1)printf("%lld",ans);else printf("-1");
}	

kruskal

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e4+7;
const int M=5e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f;
	struct Edge{
		int u,v,Next;LL w;
		bool operator<(const Edge &o)const{
			return w<o.w;
		}
	}e[M*2];
	int n,m,head[N],tot,fa[N];
	void clear(){
		memset(head,0,sizeof(head));
		tot=0;
	}
	void add(int u,int v,LL w){e[++tot]=(Edge){u,v,head[u],w};head[u]=tot;}
	void addedge(int u,int v,LL w){add(u,v,w);add(v,u,w);}
	int find(int u){
		if(fa[u]!=u)fa[u]=find(fa[u]);
		return fa[u];
	}
	LL kruskal(){
		for(int i=1;i<=n;i++)fa[i]=i;
		sort(e+1,e+1+tot);
		int Edgecnt=0;
		LL cost=0;
		for(int i=1;i<=tot;i++){
			int fu=find(e[i].u),fv=find(e[i].v);
			if(fu!=fv){
				cost+=e[i].w;
				fa[fu]=fv;
				Edgecnt++;
			}
			if(Edgecnt==n-1)break;
		}
		if(Edgecnt==n-1)return cost;
		else return -1;//非连通
	}
}G;
int main(){
	G.clear();
	cin>>G.n>>G.m;
	for(int i=1,u,v,w;i<=G.m;i++){
		scanf("%d%d%d",&u,&v,&w);
		G.add(u,v,(LL)w);
	}
	LL ans=G.kruskal();
	if(ans!=-1)printf("%lld",ans);else printf("-1");
}	

· 最短路

Floyd

多源最短路, 时间O(n^3) ,空间O(n^2)
计算所有点之间的最短路、可以计算负权图不能存路径

//邻接矩阵存图。
const ll Inf=0x7fffffff;
ll dis[N][N];
void Floyd(){
	for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		if(dis[i][j]>dis[i][k]+dis[k][j])
			dis[i][j]=dis[i][k]+dis[k][j];
}
int main(){
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)dis[i][j]=Inf; //初始化
	cin>>n;
	[...输入邻接矩阵]
	Floyd();
}

Dijkstra

计算一个点到其他所有点的最短路、适用于无负权图可以存路径

优先队列优化后复杂度O(eloge)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e4+7;
const int M=5e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f;
	struct Edge{int v,Next;LL w;}e[M*2];
	int n,m,vis[N],head[N],tot,path[N];//path记录路径
	LL dis[N];
	struct node{
		int v;LL d;
		bool operator <(const node &o)const{
			return d>o.d;
		}
	};//优先队列类型
	priority_queue<node>q; 
	void clear(){
		while(!q.empty())q.pop();
		memset(head,0,sizeof(head));
		memset(path,0,sizeof(path));
		tot=0;
	}
	void add(int u,int v,LL w){e[++tot]=(Edge){v,head[u],w};head[u]=tot;}
	void addedge(int u,int v,LL w){add(u,v,w);add(v,u,w);}
	void Dijkstra(int s){//+优先队列 
		for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);//初始化 
		q.push(node{s,0});
		while(!q.empty()){
			node now=q.top();q.pop();
			if(vis[now.v])continue;
			vis[now.v]=1; 
			for(int j=head[now.v];j;j=e[j].Next)
				if(dis[e[j].v]>dis[now.v]+e[j].w){
					dis[e[j].v]=dis[now.v]+e[j].w;
					q.push(node{e[j].v,dis[e[j].v]});
					//path[e[j].v]=now.v;	//记录路径 
				}
		}
	}
	void print_path(int x){//打印最段路径, 调用时参数为终点 
		if(path[x]==0){
			printf("%d ",x);
			return;
		}
		print_path(path[x]);
		printf("%d ",x);
	}	
}G;
int main(){
	G.clear();
	int s,t;
	cin>>G.n>>G.m>>s>>t;
	for(int i=1,u,v,w;i<=G.m;i++){
		scanf("%d%d%d",&u,&v,&w);
		G.add(u,v,(LL)w);
	}
	G.Dijkstra(s);
	printf("%lld",G.dis[t]);
}

Spfa

1. 时间复杂度比普通的Dijkstra和Ford低。期望值为O(K*E),最坏为O(V*E)
(其中K为所有顶点进队的平均次数,E是边的数量,V是点数,可以证明K一般小于等于2)
2. 计算一个点到其他所有点的最短路,可以计算负权图,能够判断是否够有负环(存在点进队超过n次即存在负环)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e4+7;
const int M=5e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f;
	struct Edge{int v,Next;LL w;}e[M*2];
	int n,m,vis[N],times[N],head[N],tot,path[N];//times记录入队次数,path记录路径
	LL dis[N];
	void clear(){
		memset(head,0,sizeof(head));
		memset(times,0,sizeof(times));
		memset(path,0,sizeof(path));
		tot=0;
	}
	void add(int u,int v,LL w){e[++tot]=(Edge){v,head[u],w};head[u]=tot;}
	void addedge(int u,int v,LL w){add(u,v,w);add(v,u,w);}
	int spfa(int s){//返回-1则有负环,0则没有 
		for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);
		vis[s]=1;	//times[s]=1;   //入队次数 
		queue<int>q;
		q.push(s);
		while(!q.empty()){
			int now=q.front();q.pop();
			vis[now]=0;			//释放
			for(int i=head[now];i;i=e[i].Next)
				if(dis[e[i].v]>dis[now]+e[i].w){ //松弛
					dis[e[i].v]=dis[now]+e[i].w;
					//path[e[i].v]=now;	//记录路径 
					if(!vis[e[i].v]){
						q.push(e[i].v);
						vis[e[i].v]=1;
						//times[e[i].v]++;
						//if(times[e[i].v]>n)return -1; //判负环
					}
				}
		}
		return 0;
	}
	void print_path(int x){//打印最段路径, 调用时参数为终点 
		if(path[x]==0){
			printf("%d ",x);
			return;
		}
		print_path(path[x]);
		printf("%d ",x);
	}	
}G;
int main(){
	G.clear();
	int s,t;
	cin>>G.n>>G.m>>s>>t;
	for(int i=1,u,v,w;i<=G.m;i++){
		scanf("%d%d%d",&u,&v,&w);
		G.addedge(u,v,(LL)w);
	}
	G.spfa(s);
	printf("%lld",G.dis[t]);
}

图论方法
1.比赛尽量用 dijkstra+优先队列,别用spfa
2.有向图求多点到单点的最短路径,让边反向即可。
3.最小生成森林,可以建立一个超级源点,连向所有的点(边权为建立点需要的代价)


二分图

· 定义

二分图中:
匹配:给定一个二分图G=<V, E>,在G的一个子图M中,M的边集E中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
最大匹配:边数最多的一个匹配
完美匹配:也称为完备匹配,指一个图中所有的顶点都是匹配点的匹配。(完美匹配一定是最大匹配,但并非每个图都存在完美匹配.)
最优匹配:最优匹配又称为带权最大匹配,是指在带有权值边的二分图中,求一个匹配使得匹配边上的权值和最大。
最小顶点覆盖:最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联。--------------------- 【最小顶点覆盖数 = 最大匹配数】
最小路径覆盖:也称为最小边覆盖,指用最少的不相交简单路径覆盖二分图中的所有顶点。 ------------- 【最小路径覆盖数 = |V|-最大匹配数】
最大独立集:指寻找一个点集,使得其中任意两点在图中无连边。--------------------------------------- 【最大独立集 = |V|-最大匹配数】
增广路: 从一个未匹配点出发,依次交替经过非匹配边、匹配边、非匹配边…,到达另一个未匹配点的路径称为增广路。
[引用链接]

· 匈牙利算法 [二分图的最大匹配]

#include<bits/stdc++.h>
using namespace std;
const int N=1007;
const int M=5e4+7;
struct Graph{
	//匈牙利算法(二分图匹配) 时间复杂度O(nx*m+ny) 
	struct Edge{
		int v,Next;
	}e[M*2];
	int nx,ny,m,ans;//nx左部点数,ny右部点数,m边数,ans匹配数 
	int head[N],tot;
	int dfn[N],match[N],tim;//tim是时间戳
	
	void clear(){
		memset(head,0,sizeof(head));
		tim=tot=ans=0;
		memset(match,0,sizeof(match));
		memset(dfn,0,sizeof(dfn));
	}
	void add(int u,int v){e[++tot]=(Edge){v,head[u]};head[u]=tot;}
	void addedge(int u,int v){add(u,v);add(v,u);}
	bool dfs(int u,int ti){//ti是时间戳 
		for(int i=head[u];i;i=e[i].Next){
			int v=e[i].v;
			if(dfn[v]!=ti){//这一轮还没有遍历到点v
				dfn[v]=ti;
				if(match[v]==0||dfs(match[v],ti)){//点v还没有被匹配 或者是 找到了一条可增广的路径
					 match[v]=u;
					 return 1;
				}
			}
		}
		return 0;
	}
	void solve(){//匹配
		for(int i=1;i<=nx;i++)
			if(dfs(i,++tim))ans++;
	}
}G;
int main(){
	G.clear();
	cin>>G.nx>>G.ny>>G.m; 
	for(int i=1,u,v;i<=G.m;i++){
		scanf("%d%d",&u,&v);
		G.addedge(u,G.nx+v);
	}
	G.solve();
	cout<<G.ans;
}


· KM算法 [带权二分图的最优匹配]

例题:hdu2255

#include<bits/stdc++.h>
using namespace std;
const int N=307;
const int M=N*N;
const int Inf=0x3f3f3f3f;
struct Graph{
	//KM算法[优化](带权二分图最佳匹配) 时间复杂度O(n^3) 
	int match[N],lx[N],ly[N],slack[N],fa[N];
	int e[N][N];//邻接矩阵存图 
	bool visx[N],visy[N];
	int n,nx,ny,ans;//n总点数,nx左部点数,ny右部点数,ans最佳匹配总权值
	
	void clear(){
		memset(e,0,sizeof(e));
		memset(match,-1,sizeof(match));
		for(int i=0;i<N;i++)
			lx[i]=ly[i]=slack[i]=fa[i]=0;
	}
	int findpath(int x){
	   	int tempDelta;
	    visx[x]=true;
    	for(int y=1;y<=ny;y++){
        	if(visy[y])continue;
        	tempDelta =lx[x]+ly[y]-e[x][y];
        	if(tempDelta==0){
           		visy[y]=true;
            	fa[y+nx]=x;
            	if(match[y]==-1)return y+nx;
            	fa[match[y]]=y+nx;//记录交替树的父亲信息(为了区别X,Y集合,Y的点都映射成n+y)
            	int res=findpath(match[y]);
            	if(res>0)return res;//返回增广路的末端叶子节点
        	}else 
			if(slack[x]>tempDelta)//统计以x为准的slack值。
            	slack[x]=tempDelta;
    	}
    	return -1;
	}
	void KM(){
    	for(int x=1;x<=nx;++x){
       		for(int i=1;i<=nx;++i)slack[i]=Inf;
       		for(int i=1;i<=nx+ny;i++)fa[i]=-1;
        	memset(visx,false,sizeof(visx));
        	memset(visy,false,sizeof(visy));//换到外面,可以保留原树
        	int fir=1;int leaf=-1;
        	while(true){
            	if(fir==1){
                	leaf=findpath(x);
                	fir=0;
            	}else{
                	for(int i=1;i<=nx;i++){
                   		if(slack[i]==0){//只接着搜有新边加入的X点
                        	slack[i]=Inf;//slack要重新清空,方以后接着用
                        	leaf=findpath(i);
                        	if(leaf>0)break;
                    	}
                	}
            	}
            	if(leaf>0){
                	int p=leaf;
                	while(p>0){
                    	match[p-nx]=fa[p];
                    	p=fa[fa[p]];//顺着记录一路找找上去
                	}
                	break;
            	}else{
                	int delta=Inf;
                	for(int i=1;i<=nx;++i)
                    	if(visx[i]&&delta>slack[i])
                        	delta=slack[i];
                	for(int i=1;i<=nx;++i)
                    	if(visx[i]){lx[i]-=delta;slack[i]-=delta;}//X点的slack要响应改变,slack变0说明有新边加入
                	for(int j=1;j<=ny;++j){
                    	if(visy[j])
                        	ly[j]+=delta;
                	}
            	}
        	}
    	}
	}
	void solve(){
   		for(int i=1;i<=nx;++i){
        	lx[i]=-Inf;
        	for(int j=1;j<=ny;++j)
            	if(lx[i]<e[i][j])lx[i]=e[i][j];
   		}
   		KM();
   		ans=0;
   		for(int i=1;i<=ny;++i)
            if(match[i]!=-1)ans+=e[match[i]][i];
	}
}G;
int main(){
	int n;
	while(cin>>n){
		G.clear();
		G.nx=G.ny=n;G.n=2*n;
		for(int i=1;i<=G.nx;i++)
			for(int j=1;j<=G.ny;j++)
				scanf("%d",&G.e[i][j]);
		G.solve();
		cout<<G.ans<<"\n";
	}
}

· 带花树算法 [一般图的最大匹配]

#include<bits/stdc++.h>
using namespace std;
const int N=1007;
const int M=N*N*2;
struct Tree_With_Flower{//Tree with flower 时间复杂度O(n^3)
	struct Edge{
		int v,Next;
	}e[M];
	int head[N],tot;
	inline void add(int u,int v){e[++tot]=(Edge){v,head[u]};head[u]=tot;}
	inline void addedge(int u,int v){add(u,v);add(v,u);}
		
	int q[M],ql,qr;
	int n,m,ans,tim,pre[N];//n点数,m边数,ans匹配数
	int dfn[N],match[N],cl[N],fa[N];
	
	inline void clear(){
		memset(head,0,sizeof(head));
		memset(match,0,sizeof(match));
		memset(dfn,0,sizeof(dfn));
		tot=ans=tim=0;
	}
	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}	
	inline int lca(int x,int y){
		for(++tim;;swap(x,y))if(x){
			x=find(x);
			if(dfn[x]==tim)return x;
			else dfn[x]=tim,x=pre[match[x]];
		}
	}
	inline void shrink(int x,int y,int p){
		while(find(x)!=p){
			pre[x]=y,y=match[x];
			if(cl[y]==2)cl[y]=1,q[++qr]=y;
			if(find(x)==x)fa[x]=p;
			if(find(y)==y)fa[y]=p;
			x=pre[y];
		}
	}
	inline bool aug(int s){
		for(register int i=1;i<=n;++i)fa[i]=i;
		memset(cl,0,sizeof(cl));memset(pre,0,sizeof(pre));
		cl[q[ql=qr=1]=s]=1;
		while(ql<=qr){
			int u=q[ql++];
			for(register int i=head[u],v=e[i].v;i;i=e[i].Next,v=e[i].v){
				if(cl[v]==2||find(v)==find(u))continue;
				if(!cl[v]){
					cl[v]=2;pre[v]=u;
					if(!match[v]){
						for(register int x=v,las,y;x;x=las)
							las=match[y=pre[x]],match[x]=y,match[y]=x;
						return 1;
					}
					cl[match[v]]=1,q[++qr]=match[v];
				}
				else if(cl[v]==1){
					int l=lca(u,v);
					shrink(u,v,l);
					shrink(v,u,l);
				}
			}
		}
		return 0;
	}
	void solve(){//匹配 
		for(int i=1;i<=n;i++)ans+=(!match[i]&&aug(i));
	}
	
}T;
int main(){
	T.clear();
	cin>>T.n>>T.m;
	for(int i=1,u,v;i<=T.m;i++){
		scanf("%d%d",&u,&v);
		T.addedge(u,v);
	}
	T.solve();
	cout<<T.ans<<"\n";
	for(int i=1;i<=T.n;i++)printf("%d ",T.match[i]);
	return 0;
}

网络流

最大流—大佬写的学习链接

· EK算法

算法复杂度O(nm2)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define R register 
const int N=1e4+7;
const int M=1e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f,ans,minflow[N];
	int n,m,start,end,vis[N],pre[N],head[N],tot;
	struct Edge{int v,Next;LL flow;}e[M*2];
	void clear(){
		memset(head,-1,sizeof(head));
		tot=ans=0;
	}
	inline void add(int u,int v,LL flow){e[tot]=(Edge){v,head[u],flow};head[u]=tot++;}//需要从e[0]开始存边 
	inline void addedge(int u,int v,LL flow){add(u,v,flow);add(v,u,0);}
	int bfs(int s,int t){
		for(R int i=0;i<=n;i++)vis[i]=0;
		queue<int>q;
		q.push(s);minflow[s]=Inf;
		while(!q.empty()){
			int now=q.front();q.pop();
			for(R int i=head[now];i!=-1;i=e[i].Next)
				if(e[i].flow&&!vis[e[i].v]){
					minflow[e[i].v]=min(minflow[now],e[i].flow);
					//用minflow[u]表示找到的路径上从s到u途径边残留容量最小值
					q.push(e[i].v);pre[e[i].v]=i;vis[e[i].v]=1;
					if(e[i].v==t)return 1;//如果达到终点,说明最短增广路已找到,结束bfs
				}
		}
		return 0;
	}
	void EK(int s,int t){
		while(bfs(s,t)==1){
			int v=t;ans+=(LL)minflow[t];//minflow[t]即为当前增广路上边残留容量最小值
			while(v!=s){
				int i=pre[v];
				e[i].flow-=minflow[t];
				e[i^1].flow+=minflow[t];//反向边
				v=e[i^1].v;//反向边指向的地方就是当前位置的父亲
			}
		}
	}
}G;
template<class T>inline void read(T &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch<'0'||ch>'9')  {f|=(ch=='-');ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x=f?-x:x;
    return;
}
int main(){
	G.clear();
	cin>>G.n>>G.m>>G.start>>G.end;
	for(int i=1,u,v,flow;i<=G.m;i++){
		read(u);read(v);read(flow);
		G.addedge(u,v,flow);
	}
	G.EK(G.start,G.end);
	printf("%lld",G.ans);
}

点少的话可以合并重边优化

· Dinic算法

DInic+当前弧优化+炸点优化
算法复杂度O(n2m);对于二分图,复杂度最坏为O(n√m)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define R register 
const int N=1e4+7;
const int M=1e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f,ans;
	int n,m,start,end,dep[N],head[N],tot,cur[M];
	struct Edge{int v,Next;LL flow;}e[M*2];
	void clear(){
		memset(head,-1,sizeof(head));
		tot=ans=0;
	}
	inline void add(int u,int v,LL flow){e[tot]=(Edge){v,head[u],flow};head[u]=tot++;}//需要从e[0]开始存边 
	inline void addedge(int u,int v,LL flow){add(u,v,flow);add(v,u,0);}
	int bfs(int s,int t){//建立层次图
		queue<int>q;
		memset(dep,-1,sizeof(dep));
		dep[s]=0;q.push(s);
		while(!q.empty()){
			int now=q.front();q.pop();
			for(R int i=head[now];i!=-1;i=e[i].Next)
				if(dep[e[i].v]==-1&&e[i].flow){
					q.push(e[i].v);
					dep[e[i].v]=dep[now]+1;
				}
		}
		return dep[t]!=-1;
	}
	LL dfs(int now,int t,LL maxflow){
		if(now==t)return maxflow;
		LL re=0; 
		for(R int i=cur[now];i!=-1;i=e[i].Next){
			if(dep[e[i].v]!=dep[now]+1||e[i].flow==0||re>=maxflow)continue;
			cur[now]=i;//当前弧优化
			LL flow=dfs(e[i].v,t,min(e[i].flow,maxflow-re));
			e[i].flow-=flow;
			e[i^1].flow+=flow;
			re+=flow;
		}
		if(!re)dep[now]=-1;//炸点优化 
		return re;
	}
	void Dinic(int s,int t){
		while(bfs(s,t)){
			memcpy(cur,head,sizeof(head));
			LL temp;
			while((temp=dfs(s,t,Inf)))ans+=temp;
		}
	}				
}G;
int main(){
	G.clear();
	cin>>G.n>>G.m>>G.start>>G.end;
	for(R int i=1,u,v,flow;i<=G.m;i++){
		scanf("%d%d%d",&u,&v,&flow);
		G.addedge(u,v,flow);
	}
	G.Dinic(G.start,G.end);
	cout<<G.ans;
}

· MCMF—最小费用最大流

实现是用EK算法+spfa,时间复杂度O(不知道),一般能解决1e4的题目

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define R register 
const int N=1e4+7;
const int M=1e5+7;
struct Graph{
	LL Inf=0x3f3f3f3f,ans,minflow[N],dis[N],ans_maxflow,ans_mincost;
	int n,m,s,t,head[N],tot,pre[N],vis[N];
	struct Edge{int v,Next;LL flow,cost;}e[M*2];
	void clear(){
		memset(head,-1,sizeof(head));
		tot=ans_maxflow=ans_mincost=0;
	}
	inline void add(int u,int v,LL flow,LL cost){e[tot]=(Edge){v,head[u],flow,cost};head[u]=tot++;}//需要从e[0]开始存边 
	inline void addedge(int u,int v,LL flow,LL cost){add(u,v,flow,cost);add(v,u,0,-cost);}
	int spfa(int s,int t){//建立层次图
		memset(dis,64,sizeof(dis));
		memset(minflow,64,sizeof(minflow));
		memset(vis,0,sizeof(vis));
		queue<int>q;
		q.push(s);vis[s]=1;dis[s]=0;pre[t]=-1;
		while(!q.empty()){
			int now=q.front();q.pop();
			vis[now]=0;
			for(int i=head[now];i!=-1;i=e[i].Next)
				if(e[i].flow>0&&dis[e[i].v]>dis[now]+e[i].cost){
					dis[e[i].v]=dis[now]+e[i].cost;
					pre[e[i].v]=i;
					minflow[e[i].v]=min(minflow[now],e[i].flow);
					if(!vis[e[i].v]){
						vis[e[i].v]=1;
						q.push(e[i].v);
					}
				}
		}
		return pre[t]!=-1;
	}
	void MCMF(int s,int t){
		while(spfa(s,t)){
			int v=t;
			ans_maxflow+=minflow[t];
			ans_mincost+=minflow[t]*dis[t];
			while(v!=s){
				e[pre[v]].flow-=minflow[t];
				e[pre[v]^1].flow+=minflow[t];
				v=e[pre[v]^1].v;
			}
		}
	}
}G;
int main(){
	G.clear();
	cin>>G.n>>G.m>>G.s>>G.t;
	for(int i=1,u,v,flow,cost;i<=G.m;i++){
		scanf("%d%d%d%d",&u,&v,&flow,&cost);
		G.addedge(u,v,flow,cost);
	}
	G.MCMF(G.s,G.t);
	printf("%lld %lld",G.ans_maxflow,G.ans_mincost);
}

Tarjan 强连通分量,缩点

代码是洛谷P3387[缩点模板题]
题意:给一个有向点权图,重复经过一个点只算一次点权,求一条路径使得路径上的点权和最大。’
思路:将强连通分量缩点,变成一个DAG,拓扑排序求最长路
时间复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;
#define next Next
const int N=1e5+7,M=1e5+7;
struct Graph{
	struct Edge{
		int u,v,next;
	}e[M];
	struct Information{//存重新建边后新节点的各种信息
		int size,val,in,out;//大小,点权,入度,出度
		Information():val(0),in(0),out(0){}
	}vertex[N];
	int n,edit_n,m,dfn[N],low[N],vis[N],head[N],tot,cnt,belong[N],id[N],value[N];
	//dfn时间戳, low能追溯到的最早的栈中节点的次序号, vis是否在栈中
	//belong属于哪个强连通分量, id缩点后的新编号
	stack<int>st;
	int sum[N],ans;//路径上的点权
	void clear(){
		memset(head,0,sizeof(head));
		for(int i=1;i<=n;i++)belong[i]=i;
		tot=cnt=edit_n=0;
	}
	void add(int u,int v){e[++tot]=Edge{u,v,head[u]};head[u]=tot;}
	void Tarjan(int u){//找强连通分量
		dfn[u]=low[u]=++cnt;
		st.push(u);
		vis[u]=1;
		for(int i=head[u];i;i=e[i].next){
			if(!dfn[e[i].v]){
				Tarjan(e[i].v);
				low[u]=min(low[u],low[e[i].v]);
			}else{
				if(vis[e[i].v])
					low[u]=min(low[u],dfn[e[i].v]);
			}
		}
		int now=0;
		if(dfn[u]==low[u]){
			id[u]=++edit_n;//给当前强连通分量新的编号
			while(now!=u){
				now=st.top();st.pop();
				vertex[id[u]].size++;
				vertex[id[u]].val+=value[now];
				vis[now]=0;
				belong[now]=u;
			}
		}
	}
	void Rebuild(){//重新建边
		memset(head,0,sizeof(head));
		tot=0;
		for(int i=1;i<=m;i++){
			int u=belong[e[i].u],v=belong[e[i].v];
			if(u!=v){
				add(id[u],id[v]);
				vertex[id[u]].out++;
				vertex[id[v]].in++;
			}
		}
		swap(n,edit_n); m=tot;//新的点数和边数
	}
	void Topo(){//拓扑排序
		queue<int>q;
		for(int i=1;i<=n;i++)
			if(vertex[i].in==0){
				q.push(i);
				sum[i]=vertex[i].val;
			}
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int i=head[u],v;i;i=e[i].next){
				v=e[i].v;
				sum[v]=max(sum[v],sum[u]+vertex[v].val);
				vertex[v].in--;
				if(vertex[v].in==0)q.push(v);
			}
		}
	}
	void Solve(){
		/* 缩点 */ 
		for(int i=1;i<=n;i++)
			if(!dfn[i])Tarjan(i);
		Rebuild();
		/* 拓扑排序求最大路径点权和 */
		Topo();
		for(int i=1;i<=n;i++)ans=max(ans,sum[i]);
	}
}G;
int main(){
	cin>>G.n>>G.m;
	G.clear();
	for(int i=1;i<=G.n;i++)scanf("%d",&G.value[i]);
	for(int i=1,u,v;i<=G.m;i++){
		scanf("%d%d",&u,&v);
		G.add(u,v);
	}
	G.Solve();//缩点
	printf("%d",G.ans);

}


倍增LCA

O ( n l o g n ) O(nlogn) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
#define next Next
const int N=1e5+7;
struct Tree{
    struct Edge{int u,v,next;}e[N<<1];
    int n,root,fa[N][30],dep[N],head[N],tot;
    void clear(){
        memset(head,0,sizeof(head));
        memset(dep,-1,sizeof(dep));
        tot=0;
    }
    void add(int u,int v){e[++tot]=Edge{u,v,head[u]};head[u]=tot;}
    void addedge(int u,int v){add(u,v);add(v,u);}
    void dfs(int u,int pre=0){
        dep[u]=dep[pre]+1;
        fa[u][0]=pre;
        for(int i=1;(1<<i)<=dep[u];i++)
            fa[u][i]=fa[fa[u][i-1]][i-1];
        for(int i=head[u];i;i=e[i].next)
            if(e[i].v!=pre)dfs(e[i].v,u);
    }
    int LCA(int u,int v){
        if(dep[u]<dep[v])swap(u,v);
        for(int i=20;i>=0;i--){
            if(dep[u]>=dep[v]+(1<<i))
                u=fa[u][i];
        }
        if(u==v)return u;
        for(int i=20;i>=0;i--)
            if(fa[u][i]!=fa[v][i]){
                u=fa[u][i];
                v=fa[v][i];
            }
        return fa[u][0];
    }
}T;
int main(){
    T.clear();
    int q;
    cin>>T.n>>q>>T.root;
    for(int i=1,u,v;i<T.n;i++){
        scanf("%d%d",&u,&v);
        T.addedge(u,v);
    }
    T.dfs(T.root);
    for(int i=1,u,v;i<=q;i++){
        scanf("%d%d",&u,&v);
        printf("%d\n",T.LCA(u,v));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linkscx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值