【模板】图论

dicnic  https://www.luogu.com.cn/problem/P2740
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue> 
#include<iostream>
#define R register int
using namespace std;
const int N=1500010;
inline void in(R &x){
	R f=1;x=0;char s=getchar();
	while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
	while(isdigit(s)){x=x*10+s-'0';s=getchar();}
	x*=f;
}
int first[N],dep[N],len=1,n,m,cur[N],st,ed;
struct cod{int y,c,gg;}b[N<<1];
inline void ins(R x,R y,R c){
    b[++len].gg=first[x];
    b[len].y=y;b[len].c=c;
    first[x]=len;
}
queue<int> q;
inline bool bfs(){
	for(int i=1;i<=n;i++) dep[i]=0;
	dep[st]=1;q.push(st); 
	while(!q.empty()){
		int x=q.front(); q.pop();
		for(R i=first[x];i>0;i=b[i].gg){
			int y=b[i].y;
			if(!dep[y] && b[i].c)
				dep[y]=dep[x]+1,q.push(y);
		}
	}return dep[ed];
}
inline int dfs(R x,R dis)
{
	if(x==ed || !dis) return dis;
	R my=0,f;
	for(R i=first[x];i>0;i=b[i].gg){
		int y=b[i].y;
		if(dep[y]==dep[x]+1 && (f=dfs(y,min(b[i].c,dis)))){
			my+=f;dis-=f;
			b[i].c-=f;b[i^1].c+=f;
			if(dis==0) return my;
		}
	}return my;
}
int main()
{
	in(m),in(n);st=1,ed=n;
	for(R x,y,c,i=1;i<=m;i++){
		in(x),in(y),in(c);
		ins(x,y,c);ins(y,x,0);
	}
	R ans=0;
	while(bfs()) ans+=dfs(st,214748364);
	printf("%d\n",ans);
	return 0;
}
最小生成树 prim算法 O(n^2)  题意:花费di标记点 或 mij连边 使得所有点被标记 
题解:prim满足贪心,第一个必须标记点,此后 边权点权取min即为代价  //不同于模板的只是原来d[i]=0
https://loj.ac/problem/10066

#include<cstdio>
bool v[310];
int ma[310][310],d[310];
int n,ans;
void Prim(){
	int minn,k;
	for(int i=1;i<=n;i++){
		minn=2147483647;
		for(int j=1;j<=n;j++)
			if(!v[j] && minn>d[j]) minn=d[j],k=j;
		
		v[k]=true;ans+=d[k];
		for(int j=1;j<=n;j++)
			if(!v[j] && d[j]>ma[j][k]) d[j]=ma[j][k];
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&d[i]);
	for(int i=1;i<=n;i++) 
		for(int j=1;j<=n;j++)
			scanf("%d",&ma[i][j]);
	Prim();		
	printf("%d",ans);
	return 0;
}
最小生成树: kruskal算法
给出树T,要求构造完全图G,使得图中唯一的一棵最小生成树是T,求完全图G的最小边权和

题解: 在kruskal的基础上,每次连当前最小的边b,显然它所连接的2个并查集中,点两两之间都必须连
一条边(完全图) 即 tot[tx]+tot[ty]-1条边 , 而新连的边不能代替b ,所以新连的边要 >b[i].c

贡献即为 (tot[tx]+tot[ty]-1)*(b[i].c+1)  + b[i].c
https://loj.ac/problem/10067

#include<cstdio>
#include<string>
#include<algorithm>
#define LL long long 
using namespace std;
inline int in(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
const int N=1e5+10;
struct bian{int x,y;LL c;}b[N];;
LL tot[N],ans=0;
int f[N];

bool cmp(bian x,bian y) {return x.c<y.c;}
int find(int x){
	if (f[x]==x) return x;
	else return f[x]=find(f[x]);
}
int main() {
	int n=in();
	for(int i=1;i<n;i++) b[i].x=in(),b[i].y=in(),b[i].c=(LL)in();
	sort(b+1,b+n,cmp);
	for(int i=1;i<=n;i++) f[i]=i,tot[i]=1;
	for(int i=1;i<n;i++){
		int fx=find(b[i].x),fy=find(b[i].y);
		if(fx!=fy){
			ans+=(tot[fx]*tot[fy]-1)*(b[i].c+1)+b[i].c;
			f[fx]=fy; 
			tot[fy]+=tot[fx]; 
		}
	}
	printf("%lld",ans);
	return 0;
}
最短路 floyd 求最小环上点的编号 special judge
枚举中转点k,d[i][j]表示以1~k-1为中转点的最短路长度(用floyd维护) 即求d[i][j]+a[i][k]+a[k][j]最小
路径的话,在floyd时,记录可行的中转点。更新答案就不停的递归找 我与中转点的中转点的中转点......
https://loj.ac/problem/10072

#include<cstdio>
#include<cstring>
int a[110][110],d[110][110],pos[110][110];//中转点
int ans[110],len=0;
int minn(int x,int y){ return x<y?x:y; }
void change(int x,int y)
{
	if(pos[x][y]==0) return ;
	change(x,pos[x][y]);
	ans[++len]=pos[x][y];
	change(pos[x][y],y);
}
int main()
{
	int n,m,x,y,c;
	long long tot=999999999;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++) a[i][j]=999999999; 
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d %d",&x,&y,&c);
		a[x][y]=a[y][x]=minn(a[x][y],c);
	}
	memcpy(d,a,sizeof(d));
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++) if(i!=k)
		for(int j=1;j<=n;j++) if(j!=k && i!=j)
		{       
			if(tot>(long long)a[i][k]+a[k][j]+d[i][j])
			{//printf("/%d %d %d/",i,j,d[i][j]+a[i][k]+a[k][j]);
				tot=(long long)a[i][k]+a[k][j]+d[i][j];
				len=0; 
				ans[++len]=i; change(i,j); 
				ans[++len]=j; ans[++len]=k;
			}
		}//一定要在其下,不然会经过两次k 
		for(int i=1;i<=n;i++) if(i!=k)
		for(int j=1;j<=n;j++)
		if(j!=k && i!=j)
		{
			if(d[i][j]>d[i][k]+d[k][j])
			{
				d[i][j]=d[i][k]+d[k][j];
				pos[i][j]=k;
			}
		}
	}if(tot==999999999) printf("No solution.");
	else for(int i=1;i<=len;i++) printf("%d ",ans[i]);
	return 0;
}
dijkstra 单源最短路
用堆维护,性质:第一次从堆中取出为最短路,第k次为第k短路(特定题目里有用,一般不拿出第二次)
重点:一定要用vis[x] 因为可能会有非最优解压在堆底,这些不能拿来更新

https://www.luogu.com.cn/problem/P5304 这题能水90
题意:图G,多标记点,求任意有标记的2点路径权值和的最小值

90分水法:对于一个被标记的点,它通过dij找到的第一个被标记就是它到 所有被标记点的距离最小值

#include<cstdio>
#include<cstring>
#include<queue>
#define LL long long
using namespace std;
struct bian{int y,gg;LL c;}b[500100];
int first[100100],len=0;
LL maxl=1ll<<60;
void ins(int x,int y,LL c){
    b[++len].y=y;
    b[len].gg=first[x];b[len].c=c;
    first[x]=len;
}
int read(){
	int x=0;char c=getchar();
	while(c<48) c=getchar();
	while(c>47) x=x*10+c-'0',c=getchar();
	return x;
}
struct node{
	int id;LL c;
	friend bool operator < (const node &x, const node &y) 
    { return x.c>y.c; }
};
priority_queue<node> q;
bool r[100100];
int p[100100],n;
LL dis[100100];
bool B[100100],v[100100];
LL dijkstra(int st)
{
   	while(!q.empty()) q.pop();
   	node cur;
   	for(int i=1;i<=n;i++) dis[i]=maxl,v[i]=false;
	cur.id=st,cur.c=0;dis[st]=0;
   	q.push(cur);
	while(!q.empty())
	{
		int x=q.top().id;q.pop();
		if(x!=st && r[x]) return dis[x];
		if(!v[x])
		{
			v[x]=true;
			for(int i=first[x];i>0;i=b[i].gg)
			{
				int y=b[i].y;
				cur.id=y,cur.c=b[i].c+dis[x];
				if(cur.c<dis[y]){
					dis[y]=cur.c;	
					q.push(cur); 
				}
			}
		}
	}return maxl;
}
LL minn(LL x,LL y) { return x<y?x:y; }
int main()
{
	int T=read();
	while(T--)
	{
		len=0;LL ans=maxl;
		memset(first,0,sizeof(first));
		memset(r,false,sizeof(r));
		n=read();
		int m=read(),k=read(),x;
		
		for(int i=1;i<=m;i++){
			int x=read(),y=read(),c=read();
			ins(x,y,(LL)c);
		}
		for(int i=1;i<=k;i++) p[i]=read(),r[p[i]]=true;
		for(int i=1;i<=k;i++) ans=minn(ans,dijkstra(p[i]));
		printf("%lld\n",ans);
	}
	return 0;
}

dijkstra 多源最短路
将所有特殊点都扔进堆里!
将所有特殊点都扔进堆里!
将所有特殊点都扔进堆里!

靠染色配合跑多源最短路 , 跑某个点 被所有特殊点到达的路 中的最短路 记录来源被标记点col
再反过来建边  即某个点到任意一个特殊点的最短路   记录来源相当于记录去向被标记点
来源与去向一定都是被标记点,对于每一条有向边,若来源与去向不同,则可更新答案ans

还是这道题的正解之一 https://www.luogu.com.cn/problem/P5304 
代码来自 https://www.luogu.com.cn/blog/Owencodeisking/solution-p5304

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100000+10;
const int maxm=500000+10;
const ll inf=0x3f3f3f3f3f3f3f3f;	
int n,m,k,a[maxn],X[maxm],Y[maxm],W[maxm],col[2][maxn],head[maxn],tot;
ll dis[2][maxn],ans;bool vis[maxn],iscity[maxn];

struct Edge{int to,next,val;}e[maxm];
struct node{
	ll dis;int id;
	node(ll _dis=0,int _id=0):dis(_dis),id(_id){}
};
inline bool operator < (const node &a,const node &b){return a.dis>b.dis;}
inline int read(){
	register int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return (f==1)?x:-x;
}
inline void addedge(int x,int y,int w){	
	e[++tot].to=ye[tot].val=w;
	e[tot].next=head[x];head[x]=tot;
}
inline void Dijkstra(ll *dis,int *col){
	for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
	priority_queue<node> pq;
	
	for(int i=1;i<=k;i++) dis[a[i]]=0 , col[a[i]]=a[i] , pq.push(node(0,a[i]));

	while(!pq.empty()){
		int u=pq.top().id;pq.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].val)
			{
				dis[v]=dis[u]+e[i].val;
				col[v]=col[u];
				pq.push(node(dis[v],v));
			}
		}
	}
}
inline void solve()
{
	n=read(),m=read(),k=read();
	int x,y,w;
	for(int i=1;i<=m;i++)
	{
		x=read(),y=read(),w=read();
		if(x!=y) addedge(x,y,w);
		X[i]=x;Y[i]=y;W[i]=w;
	}
	for(int i=1;i<=k;i++) a[i]=read(),iscity[a[i]]=1;
	Dijkstra(dis[0],col[0]);
	for(int i=1;i<=n;i++) head[i]=0;
	tot=0;
	for(int i=1;i<=m;i++)
		if(X[i]!=Y[i]) addedge(Y[i],X[i],W[i]);
	Dijkstra(dis[1],col[1]);
	ans=inf;
	for(int i=1;i<=m;i++){
		x=X[i];y=Y[i];w=W[i];
		if(col[0][x]&&col[1][y]&&col[0][x]!=col[1][y]) ans=min(ans,dis[0][x]+dis[1][y]+w);
	}
	printf("%lld\n",ans);
	for(int i=1;i<=n;i++) head[i]=iscity[i]=0;
	tot=0;
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}
spfa 求来回最短路 
重点:v[x]判断i是否在队列中
0表示从st出发的最短路
1表示到st的最短路,将边反过来建,2次spfa
https://loj.ac/problem/10075

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
bool v[21100];
int d[2][21100],first[21100],len=0,st,n,m,ans=0;
struct bian{int x,y,c,gg;}b[21100];
void Clear(int num){
	len=0;
	for(int i=1;i<=n;i++){
		first[i]=0;
		v[i]=false;
	 	d[num][i]=999999999;
	}
}
void ins(int x,int y,int c){
	b[++len].y=y;	b[len].c=c;
	b[len].gg=first[x];	first[x]=len;
}
queue<int> q; 
void spfa(int num)
{
	v[st]=true; d[num][st]=0;
	q.push(st);
	while(!q.empty()){
		int x=q.front();q.pop(); v[x]=false; 
		for(int i=first[x];i>0;i=b[i].gg){
			int y=b[i].y;
			if(d[num][y]>d[num][x]+b[i].c){
				d[num][y]=d[num][x]+b[i].c;
				if(!v[y]){
					v[y]=true;
					q.push(y); 
				}
			}
		}
	}
}
int main()
{
	scanf("%d %d %d",&n,&m,&st);
	int x[100100],y[100100],c[100100];
	Clear(0); 
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&x[i],&y[i],&c[i]);
		ins(x[i],y[i],c[i]);
	}spfa(0); 
	Clear(1); 
	for(int i=1;i<=m;i++) ins(y[i],x[i],c[i]);
	spfa(1); 
	for(int i=1;i<=n;i++){
		 if(ans<d[1][i]+d[0][i]) ans=d[1][i]+d[0][i];
	}printf("%d",ans);
	return 0;
}
Tarjan 割点
判定法则:dfn[x]<=low[y]  且 root没有父亲树,所以要有两棵子树不连通 
https://www.luogu.com.cn/problem/P3388#submit

#include<cstdio>
#include<cstring>
struct bian{int y,gg;}b[200100];
int first[20100],len=0;
void ins(int x,int y){
	len++;b[len].y=y;
	b[len].gg=first[x];
	first[x]=len;
}
int dfn[20100],low[20100],num=0,root,tot=0;
bool v[20100];
int mymin(int x,int y) { return x<y?x:y; }
void tarjan(int x){
	dfn[x]=low[x]=++num;int flag=0;
	for(int i=first[x];i>0;i=b[i].gg){
		int y=b[i].y;
		if(!dfn[y]){
			tarjan(y);
			low[x]=mymin(low[x],low[y]);
			if(dfn[x]<=low[y]){//可以连上x,反正所有与x相连的边都要被删除 
				flag++;			//subtree(x)连不上去,所以subtree(x)会与父亲树断开,
				if(x!=root || flag>1) v[x]=true;//而root没有父亲树,所以要有两棵子树不连通 
			}
		}//无需考虑重边
		else low[x]=mymin(low[x],dfn[y]);//可以连上fa,反正所有与fa相连的边都要被删除 
	}
}
int main()
{
	int n,m,x,y;
	memset(dfn,0,sizeof(dfn));
	memset(first,0,sizeof(first));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&x,&y);
		if(x==y) continue;
		ins(x,y);ins(y,x);
	}
	for(int i=1;i<=n;i++) 
		if(!dfn[i]) root=i,tarjan(i);
	for(int i=1;i<=n;i++) if(v[i]) tot++;
	printf("%d\n",tot);
	for(int i=1;i<=n;i++) if(v[i])printf("%d ",i);
	return 0;
}
Tarjan 割边 
判定法则:dfn[x]<low[y]   考虑重边if(i!=(inedge^1)) low[x]=mymin(low[x],dfn[y]);

void tarjan(int x,int inedge){
	dfn[x]=low[x]=++num;int flag=0;
	for(int i=first[x];i>0;i=b[i].gg){
		int y=b[i].y;
		if(!dfn[y]){
			tarjan(y,i);
			low[x]=mymin(low[x],low[y]);
			if(dfn[x]<low[y]) bridge[i]=bridge[i^1]=true;
		}//考虑父亲边
		else if(i!=(inedge^1)) low[x]=mymin(low[x],dfn[y]);
	}
}
int main()
{
	for(int i=1;i<=n;i++)  if(!dfn[i]) tarjan(i,0);
}
Tarjan 强连通分量 判定法则:深搜结束后low[x]==dfn[x] 把栈内x之上的弹出
if(!id[y]) low[x]=minn(low[x],dfn[y]);//还在栈内 

https://www.luogu.com.cn/problem/P2341
思路:强连通之后拓扑求是否有出度 
连通两个强连通分量的边一定是桥,也就是说只有可能一整个环喜欢另一个环,而无法做到互相喜欢 
森林一定是错的,要所有嘛 

1.过桥后封闭
2.过桥后又有桥连向别的世界,直到封闭,之前走过的存在栈里
8字形的环属于同一个环(自己模拟) 

#include<cstdio>
using namespace std;
int first[10010];
struct bian{int gg,y;}b[50010];
int num=0,dfn[10010],low[10010],du[10010],dcc[10010],dl=0;
int len=0,id[10010],n,m;
int top,sta[10010];
void ins(int x,int y)
{
    b[++len].y=y;
    b[len].gg=first[x];
    first[x]=len;
}
int minn(int x,int y){ return x<y?x:y; }
void Tarjan(int x)
{
    dfn[x]=low[x]=++num;
    sta[++top]=x;id[x]=0;//入栈标记 同时是所属强连通分块标记 
    for(int i=first[x];i!=0;i=b[i].gg){
        int y=b[i].y;
        if(!dfn[y]){
            Tarjan(y);
            low[x]=minn(low[x],low[y]);
        }
        else if(!id[y]) low[x]=minn(low[x],dfn[y]);//还在栈内 
    }
    if(low[x]==dfn[x])//过桥相当于另一个世界,假设为情况一,过桥之后回不来,那边dfn最小的为守桥人x 
    {
		dl++;
        do 
        {
			id[sta[top]]=dl;//sta[top]这个点属于第dl个强连通分块 
            ++dcc[dl];
           // printf("%d",sta[top]);
        }while(sta[top--]!=x);//所以会把那边世界高于x的点都弹掉,成为一个连通分量 
    }
}
int main()
{
    int x,y;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d %d",&x,&y),ins(x,y);
        
    for(int i=1;i<=n;i++)
        if(!dfn[i]) Tarjan(i);
    for(int i=1;i<=n;i++)
    for(int j=first[i];j!=0;j=b[j].gg){
        if(id[i] != id[b[j].y]) 
		du[id[i]]++;
    }                   
            
    int ans=0,tot=0;
    for(int i=1;i<=dl;i++) if(du[i]==0) ans=dcc[i],tot++;
    if(tot==1)printf("%d\n",ans);
    else printf("0\n");
 	return 0;
}
匈牙利算法 最小覆盖点集、最大二分图匹配
bool dfs(int x){ 
	for(int i=first[x];i;i=b[i].gg){
		int y=b[i].y;
		if(!v[y]){
			v[y]=true;
			if(!bf[y] || dfs(bf[y])){
				bf[y]=x;return true;
			}
		}
	}return false;
}
int main(){
	for(int i=1;i<=n;i++){
		memset(v,false,sizeof(v));
		if(dfs(i)) ans++;
	}
}
欧拉回路
2-SAT
费用流
最小割
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值