NOIP图论汇总

NOIP图论Summary

NOIP2013 货车运输

算法:最大生成树,倍增LCA
思路:保证图联通以后,发现小于最大生成树最小边的边都肯定不会走
所以直接建最大生成树,然后每次询问x,y的路径最小值,直接在求lca的时候一起维护就可以。
代码:

#include<bits/stdc++.h>
using namespace std;
const int INF=1e9+7;
int n,m;
int fa[10100];
struct node{
	int x,y,z;
}R[500100];
struct xxx{
	int nxt,v,val;
}e[500100*2];
int cnt,ans;
int anc[10010][30];
int wei[10010][30];
int vis[10010];
int dep[10010];
int head[10010];
void add(int u,int v,int val){
	e[++cnt].nxt=head[u];
	e[cnt].v=v;
	e[cnt].val=val;
	head[u]=cnt;
}
bool cmp(node x,node y){
	return x.z>y.z;
}
int getf(int p){
	return fa[p]==p?p:fa[p]=getf(fa[p]);
}
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void dfs(int u,int p,int d){
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==p) continue;
		if(vis[v]==1) continue;
		wei[v][0]=e[i].val;
		anc[v][0]=u;
		dep[v]=d+1;
		dfs(v,u,d+1);
	}
}
void init(){
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			dep[i]=1;
			dfs(i,0,1);
			anc[i][0]=0;
			wei[i][0]=INF;
		}
	}
	for(int j=1;j<=20;j++){
		for(int i=1;i<=n;i++){
			anc[i][j]=anc[anc[i][j-1]][j-1];
			wei[i][j]=min(wei[i][j-1],wei[anc[i][j-1]][j-1]);
		}
	}
}
void swim(int &x,int h){
	for(int i=0;h>0;i++){
		if(h&1){
			ans=min(ans,wei[x][i]);			
			x=anc[x][i];
		}
		h>>=1;
	}
}
int lca(int u,int v){
	if(getf(u)!=getf(v)) return -1;
	ans=INF;
	if(dep[u]<dep[v]) swap(u,v);
	swim(u,dep[u]-dep[v]);
	if(u==v) return ans;
	for(int i=20;i>=0;i--){
		if(anc[u][i]!=anc[v][i]){
			ans=min(ans,min(wei[u][i],wei[v][i]));
			u=anc[u][i];
			v=anc[v][i];
		}
	}
	ans=min(ans,min(wei[u][0],wei[v][0]));
	return ans;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		R[i].x=read();
		R[i].y=read();
		R[i].z=read();
	}
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	sort(R+1,R+m+1,cmp);
	for(int i=1;i<=m;i++){
		int f1=getf(R[i].x);
		int f2=getf(R[i].y);
		if(f1!=f2){
			fa[f1]=f2;
			add(R[i].x,R[i].y,R[i].z);
			add(R[i].y,R[i].x,R[i].z);
		}
	}
	init();
	int q;
	q=read();
	while(q--){
		int x,y;
		x=read();
		y=read();
		printf("%d\n",lca(x,y));
	}
	return 0;
}

NOIP2009 最优贸易

算法:缩点+DAG dp
思路:发现一个强连通分量中的点都可以互相到达,直接缩点,维护缩点后的每个点最大和最小的价格
然后就可以在DAG上dp,dp[i]表示能走到i点的所有贸易方案中获利最大值,再记f[i]表示能走到i的最小价格
直接转移即可
答案为dp[belong[n]];

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=500000;
inline int read(){
	char c=getchar();int x=0,f=1;
	while(c<'0'||c>'9'){ if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
int n,m;
int a[maxn];
int dfn[maxn];
int low[maxn];
int cnt1,top,dep,sum,cnt2;
int stack[maxn];
int head1[maxn];
int belong[maxn];
int head2[maxn];
int maxv[maxn];
int minv[maxn];
int vis[maxn];
int in[maxn];
int topo[maxn];
int dp[maxn];
int f[maxn];
struct node{
	int nxt,v;
}e1[maxn*2],e2[maxn*2];
void add1(int u,int v){
	e1[++cnt1].nxt=head1[u];
	e1[cnt1].v=v;
	head1[u]=cnt1;
}
void add2(int u,int v){
	e2[++cnt2].nxt=head2[u];
	e2[cnt2].v=v;
	head2[u]=cnt2;
}
void tarjan(int u){
	dfn[u]=low[u]=++dep;
	vis[u]=1;
	stack[++top]=u;
	for(int i=head1[u];i;i=e1[i].nxt){
		int v=e1[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[v],low[u]);
		}
		else{
			if(vis[v]){
				low[u]=min(dfn[v],low[u]);
			}
		}
	}
	if(dfn[u]==low[u]){
		belong[u]=++sum;
		vis[u]=0;
		minv[sum]=1e9+7;
		while(stack[top]!=u){
			belong[stack[top]]=sum;
			vis[stack[top]]=0;
			maxv[sum]=max(maxv[sum],a[stack[top]]);
			minv[sum]=min(minv[sum],a[stack[top]]);
			top--;
		}
		maxv[sum]=max(maxv[sum],a[u]);
		minv[sum]=min(minv[sum],a[u]);
		top--;
	}
}
int main(){
//	freopen("work.in","r",stdin);
//	freopen("work.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	int x,y,z;
	for(int i=1;i<=m;i++){
		x=read(),y=read(),z=read();
		if(z==1){
			add1(x,y);
		}
		else{
			add1(x,y);
			add1(y,x);
		}
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			tarjan(i);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=head1[i];j;j=e1[j].nxt){
			int v=e1[j].v;
			if(belong[i]!=belong[v]){
				add2(belong[i],belong[v]);
				in[belong[v]]++;
			}
		}
	}
	int tail=0;
	for(int i=1;i<=sum;i++){
		if(in[i]==0){
			topo[++tail]=i;
		}
	}
	for(int i=1;i<=tail;i++){
		int now=topo[i];
		for(int j=head2[now];j;j=e2[j].nxt){
			int v=e2[j].v;
			in[v]--;
			if(in[v]==0){
				topo[++tail]=v;
			}
		}
	}
	f[topo[1]]=minv[topo[1]];
	for(int i=1;i<=tail;i++){
		int u=topo[i];
		for(int j=head2[u];j;j=e2[j].nxt){
			int v=e2[j].v;
			f[v]=min(minv[v],f[u]);
			dp[v]=max(max(dp[v],dp[u]),max(maxv[v]-minv[v],maxv[v]-f[v]));
		}
	}
	cout<<dp[belong[n]]<<endl;
	return 0;
}

NOIP2018 旅行

思路:
关于树,直接dfs,每次先搜叶子点里最小的点,O(n)
关于基环树,先类似于tarjan的方法找到环,记录下环
发现一定有一条环上的边不会走到
枚举一条环上边,删掉
再跑和树一样的dfs
找到最小的方案,O(n^2)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=10000;
inline int read(){
	char c=getchar();int x=0,f=1;
	while(c<'0'||c>'9'){ if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
int n,m,cnt,top,dep,stp,cntl,flag,back;
int head[maxn];
struct node{
	int v,nxt;
}e[maxn*2];
void add(int u,int v){
	e[++cnt].nxt=head[u];
	e[cnt].v=v;
	head[u]=cnt;
}
int dfn[maxn];
int lop[maxn];
int faa[maxn];
int ans[maxn];
void dfs1(int u,int fa){
	ans[++top]=u;
	int vis[100];
	memset(vis,0,sizeof(vis));
	int cnt2=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa) continue;
		vis[++cnt2]=v;
	}
	sort(vis+1,vis+cnt2+1);
	for(int i=1;i<=cnt2;i++){
		dfs1(vis[i],u);
	}
	return;
}
void find_loop(int u,int fa){
	dfn[u]=++dep;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa) continue;
		if(dfn[v]){
			if(dfn[u]>dfn[v]) stp=v;
			lop[++cntl]=u;
			for(;v!=stp;v=faa[v]) lop[++cntl]=v;
		}
		else{
			faa[v]=u;
			find_loop(v,u);
		}
	}
	return;
}
void dfs2(int u,int fa,int bu,int bv){
	int vis[100];
	if(back) return;
	top++;
	if(u<ans[top]) flag=1;
	if(u>ans[top]&&flag==0) {
		back=1;return;	
	}
	if(flag) ans[top]=u;
	memset(vis,0,sizeof(vis));
	int cnt2=0;
	int ban=0;
	if(u==bu) ban=bv;
	if(u==bv) ban=bu;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa||v==ban) continue;
		vis[++cnt2]=v;
	}
	sort(vis+1,vis+cnt2+1);
	for(int i=1;i<=cnt2;i++){
		dfs2(vis[i],u,bu,bv);
	}
	return;
}
int main(){
	n=read(),m=read();
	int u,v;
	for(int i=1;i<=m;i++){
		u=read(),v=read();
		add(u,v);
		add(v,u);
	}
	if(n-1==m){
		dfs1(1,1);
		for(int i=1;i<=n;i++){
			cout<<ans[i]<<' ';
		}
		cout<<endl;
		return 0;
	}
	if(n==m){
		find_loop(1,1);
		memset(ans,0x3f,sizeof(ans));
		for(int i=1;i<cntl;i++){
			flag=0;
			top=0;
			back=0;
			dfs2(1,1,lop[i],lop[i+1]);
		}
		for(int i=1;i<=n;i++){
			cout<<ans[i]<<' ';
		}
		cout<<endl;
		return 0;
	}
	return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值