图论例题合集(二)

目录

 

A:LightOJ - 1123 Trail Maintenance

B:LightOJ - 1380 Teleport

C:LightOJ - 1250 Village Postman

D:LightOJ - 1003 Drunk

E:LightOJ - 1149 Factors and Multiples

F:LightOJ - 1152 Hiding Gold

G:LightOJ - 1429 Assassin`s Creed (II)

H:LightOJ - 1154 Penguins


A:LightOJ - 1123 Trail Maintenance:题意:给定包含 n 个点的空图,依次加入 m 条带权边,每次加入一条边,就输出当前图中最小生成树的权值,如果没有生成树,则输出-1。思路:增量最小生成树模板题

#include <bits/stdc++.h>
using namespace std;
const  int maxn = 5000;
const int INF = 1 << 25;
int n, m, u[maxn], v[maxn], w[maxn], eg[maxn], fa[maxn];
void Make_set()
{
	for (int i = 0; i <= n; i++)
		fa[i] = i;
}
int Find(int x)
{
	if (x == fa[x]) return x;
	return fa[x] = Find(fa[x]);
}
bool cmp(int a, int b)
{
	return w[a] < w[b];
}
int num;
int Kru()
{
	int d = -1, i, ans = 0, cnt = 0;
	Make_set();
	for (i = 0; i < num; i++)
		eg[i] = i;
	sort(eg, eg + num, cmp);
	for (i = 0; i < num; i++)
	{
		int e = eg[i];
		int fx = Find(u[e]);
		int fy = Find(v[e]);
		if (fx == fy)
		{
			d = e;
			continue;
		}
		fa[fx] = fy;
		ans += w[e];
		cnt++;
	}
	if (d != -1)//删边
	{
		num--;
		u[d] = u[num];
		v[d] = v[num];
		w[d] = w[num];
	}
	if (cnt == n - 1)
		return ans;
	else
		return -1;
}
int main()
{
	int T, cas = 1;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d%d", &n, &m);
		printf("Case %d:
", cas++);
		num = 0;//边数
		for (int i = 0; i < m; i++)
		{
			scanf("%d%d%d", &u[num], &v[num], &w[num]);
			num++;
			printf("%d
", Kru());
		}
	}
	return 0;
}

B:LightOJ - 1380 Teleport:有n个城市,m条单向道路,开始你在k号城市,现在要你从k开始出发,访问每个城市至少一次,就最少的花费,不行的话就impossible,这样看的话就是搜索了,,,但是题目中还给出了条件的,,,如果x号城市你之前访问过了的,那么你可以从你现在所在城市不花时间的瞬移到x号城市去。这样一来就不再是简单的搜索了,而是最小树形图了。最小树形图的模板题。

#include<stdio.h>
#include<string.h>
#include<vector>
#include<queue>
#include<algorithm>
#define INF 1<<31-1
using namespace std;
struct Edge
{
	int u;
	int v;
	int w;
}edge[10010];
int in[1010];
int id[1010];
int vis[1010];
int pre[1010];
int minTreeGra(int root, int n, int m)  
{  
    int ret = 0;  
    while(true)  
    {  
        //找最小入边  
        for(int i = 0; i < n; i++) in[i] = INF;  
        for(int i = 0; i < m; i++)  
        {  
            int u = edge[i].u;  
            int v = edge[i].v;  
            if(edge[i].w < in[v] && u != v) {pre[v] = u; in[v] = edge[i].w;}     //去除自环和重边 
        }  
        for(int i = 0; i < n; i++)  
        {  
            if(i == root) continue;  
            if(in[i] == INF) return -1;                                 //除了跟以外有点没有入边,则根无法到达它  
        }  
        
        int cnt = 0;                             //找环 
        memset(id, -1, sizeof(id));  
        memset(vis, -1, sizeof(vis));  
        in[root] = 0;  
        for(int i = 0; i < n; i++) 
        {  
            ret += in[i];  
            int v = i;  
            while(vis[v] != i && id[v] == -1 && v != root)  //每个点寻找其前序点,要么最终寻找至根部,要么找到一个环  
            {  
                vis[v] = i;  
                v = pre[v];  
            }  
            if(v != root && id[v] == -1)//缩点  
            {  
                for(int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;  
                id[v] = cnt++;  
            }  
        }  
        if(cnt == 0) break; //无环   则break  
        for(int i = 0; i < n; i++)  
            if(id[i] == -1) id[i] = cnt++;  
                                           //建新图  
        for(int i = 0; i < m;)  
        {  
            int u = edge[i].u;  
            int v = edge[i].v;  
            edge[i].u = id[u];  
            edge[i].v = id[v];  
            if(id[u] != id[v]) edge[i++].w -= in[v];
			else edge[i]=edge[--m]; 
        }  
        n = cnt;  
        root = id[root];  
    }  
    return ret;  
} 
int main()
{
	int i,t,k,u,v,cost,n,m,c=0;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d",&n,&m,&k);
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&u,&v,&cost);
			edge[i].u=u;
			edge[i].v=v;
			edge[i].w=cost;
		}
		int res=minTreeGra(k,n,m);
		if(res!=-1)
		printf("Case %d: %d
",++c,res);
		else
		printf("Case %d: impossible
",++c);
	}
	return 0;
} 

C:LightOJ - 1250 Village Postman:题意:找欧拉回路。思路:欧拉回路的模板题。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <algorithm>
#include <cmath>
#include <map>
#define LL long long
#define DB double
 
using namespace std;
const int N = 209;
const int M = 1009;
const int INF = 0x3f3f3f3f;
int n,m;
int re[N];
int ans;
int path[M],cnt;
int in[N];
struct LT{
    int to,nex;
} L[M<<1];
int F[N],C=2;
int v[M];
void add(int f,int t){
    L[C].nex = F[f];L[C].to = t;
    F[f] = C++;
}
void dfs(int k){
    for(int i=F[k];i;i=L[i].nex){
        if(v[i>>1]) continue;
        int to = L[i].to;
        v[i>>1] = 1;
        dfs(to);
    }
    path[cnt++] = k;
}
void solve(int T)
{
    ans =0;
    for(int i=1;i<=n;i++)
        ans += re[i];
    ans = ans-(n+1)*n/2-m;
    cnt = 0;
    in[1]--;
    dfs(1);
    printf("Case %d: %d
",T,ans);
 
    for(int i=0;i<cnt;i++)
    {
        if(i) printf(" ");printf("%d",path[i]);
    }puts("");
}
int main()
{
    int cas,T=1;scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d",&n,&m);
        ans =0;cnt = 0;C=2;
        memset(in,0,sizeof(in));
        memset(F,0,sizeof(F));
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&re[i]);
        }
        int a,b;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            add(a,b);add(b,a);
            in[a]++;in[b]++;
        }
        solve(T);T++;
    }
    return 0;
}

D:LightOJ - 1003 Drunk: 给出t组样例每组有m行。每行有a,b两种饮品。其中a是b的前置条件。(a-->b)在给定的m行信息中能否喝到全部的饮料,如果可以喝到全部种类的饮料则输出YES否则输出NO。思路:并查集判断是否存在环。

#include <bits/stdc++.h>
using namespace std;
int p[11111],sum;
int f(int x)//查找祖先
{
    if(p[x]==x) return x;
    return p[x]=f(p[x]);//路径压缩
}
void mix(int a,int b)
{
    int x=f(a);
    int y=f(b);
    if(x!=y)    //判断祖先是否相同,不同则合并
        p[y]=x;
    else        //相同则出现环
        sum++;
}
int main()
{
    int t,cas=1;
    scanf("%d",&t);
    while(t--)
    {
        char s1[66],s2[66];
        map<string,int> a;
        a.clear();
        int m,num=1;
        scanf("%d",&m);
        sum=0;
        for(int i=0;i<11111;i++) p[i]=i;
        for(int i=0;i<m;i++)
        {
            scanf(" %s %s",s1,s2);
            if(!a[s1])  a[s1]=num++;
            if(!a[s2])  a[s2]=num++;
            mix(a[s1],a[s2]);
        }
        if(sum==0)
            printf("Case %d: Yes
",cas++);
        else
            printf("Case %d: No
",cas++);
    }
}

E:LightOJ - 1149 Factors and Multiples:题意:给你两个集合A,和B,问你最少删除集合A和B中的几个数才能使得集合B中不存在可以整除集合A中的数,将该题转换为二分图的最大匹配问题即可,集合A为左边的点,集合B为右边的点,如果B集合中的某个数可以整除A集合中的某个数则连一条边,如何才能使得A,B之间不存在这种整除关系呢,我们找出最小顶点覆盖即可,(最小顶点覆盖就是最小的顶点数集合V,能使该图中每一条边都至少有一个顶点属于V)如果将最小顶点覆盖数删除,则图中所有的边都将不存在,意思就是不在存在这种整除关系。    在二分图中,最大匹配数等于最小顶点覆盖数。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=110;
int n,m;
int p[maxn];
int numa[maxn];
int numb[maxn];
bool vis[maxn];
bool find(int k){
	for(int i=0;i<m;++i){
		if(!vis[i]&&numb[i]%numa[k]==0){
			vis[i]=true;
			if(p[i]==-1||find(p[i])){
				p[i]=k;
				return true;
			}
		}
	}
	return false;
}
int main()
{
	int i,j,k=1,t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(i=0;i<n;++i){
			scanf("%d",&numa[i]);
		}
		scanf("%d",&m);
		for(i=0;i<m;++i){
			scanf("%d",&numb[i]);
		}
		int ans=0;
		memset(p,-1,sizeof(p));
		for(i=0;i<n;++i){
			memset(vis,false,sizeof(vis));
			if(find(i))ans++;
		}
		printf("Case %d: %d
",k++,ans);
	}
	return 0;
}

F:LightOJ - 1152 Hiding Gold:题目:n*m的方格中有一些金块,要求用1*2的多米诺骨牌覆盖他们,问最少要多少金块。思路:将那些有金块的点进行标号,标完号以后扫一遍图,对于一个金块的上下左右,只要有金块,就连上一条边,跑一遍匈牙利,最后得出的匹配数还不是答案,首先应该将匹配数除以2,这才是真正匹配到的,然后应该将金块总数减去匹配数。

#include<stdio.h>
#include<string.h>
int a[500][500],b[500],match[500];
char s[100][100];
int m,n;
int dfs(int s)//匈牙利算法
{
	int i;
	for(i=1;i<=m*n;i++)
	{
		if(b[i]==0&&a[s][i]==1)
		{
			b[i]=1;
			if(match[i]==-1||dfs(match[i]))
			{
				match[i]=s;
		//		match[s]=i;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	int i,j,k,t,t1,t2,t3,t4=1,x,y,sum;
	int next[4][2]={{0,1},{1,0},{-1,0},{0,-1}};
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		memset(a,0,sizeof(a));
		memset(s,0,sizeof(s));
		memset(match,-1,sizeof(match));
		
		for(i=1;i<=n;i++)
		{
			scanf("%s",s[i]+1);
		}
		
		t1=0;
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=m;j++)
			{
				if(s[i][j]=='*')
				{
					t1++;
					
					for(k=0;k<=3;k++)//搜索四个方向
					{
						x=i+next[k][0];
						y=j+next[k][1];
						if(x>n||y>m||x<1||y<1)
							continue;
						if(s[x][y]=='*')
						{
						
							a[(i-1)*m+j][(x-1)*m+y]=1;
						}
					}
				}
			}
		}
	
		
		sum=0;
		for(i=1;i<=m*n;i++)
		{
			memset(b,0,sizeof(b));
			if(dfs(i))
				sum++;
		}
	
		printf("Case %d: %d
",t4++,t1-sum/2);
	}
	return 0;
}

G:LightOJ - 1429 Assassin`s Creed (II):题意:有n个顶点组成的有向图,每个顶点都有一个目标,现在要派刺客去杀这些目标,刺客可以从任意点出发,且可以多次路过一个顶点,问至少要派 多少刺客可以杀死所有的目标。思路:该题是一个最小路径覆盖的问题,由于可能有环所以要用tarjian算法找出环并缩点,因为该题说可以重复 经过一个顶点,所以我们要在求最小路径覆盖之前要先预处理顶点的连通性。 在二分图里  最小路径覆盖=顶点数-最大匹配。

#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
#include<stack>
#define INF 1<<31-1
using namespace std;
vector<int>g[1010];
vector<int>newG[1010];
int used[1010];
int match[1010];
int dfn[1010];              //记录初次访问该顶点的时间 
int low[1010];             //记录顶点能达到的最早的时间 
int instack[1010];        //标记是否在栈内 
int time;                 //时间戳 
int cnt;                  //强连通分量个数 
int  belong[1010] ;        //记录顶点所属强连通分量 
void bfs(int s)             //预处理顶点的连通性
{
	queue<int>Q;
	Q.push(s);
	used[s]=1;
	while(!Q.empty())
	{
		int u=Q.front();
		Q.pop();
		for(int i=0;i<newG[u].size();i++)
		{
			int v=newG[u][i];
			if(!used[v])
			{
				used[v]=1;
				newG[s].push_back(v);
				Q.push(v);
			}
		}
	}
}
stack<int>S;
void tarjan(int u)
{
	dfn[u]=low[u]=++time;
	S.push(u);
	instack[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(dfn[v]==-1)
		{
			tarjan(v);
			low[u]=min(low[v],low[u]);
		}
		else if(instack[v]==1)
		{
			low[u]=min(dfn[v],low[u]);
		}
	}
	if(dfn[u]==low[u])
	{
		int a;
		cnt++;
		do
		{
			a=S.top();
			S.pop();
			instack[a]=0;
			belong[a]=cnt;
		}while(a!=u); 
	}
}
void suodian(int n)          //缩点,建新图 
{
	int i,j,u,v;
	for(i=1;i<=n;i++)
	{
		u=belong[i];
		for(j=0;j<g[i].size();j++)
		{
			v=belong[g[i][j]];
			if(u!=v)
				newG[u].push_back(v);
		}
	}
}
 bool find(int u)
 {
 	for(int i=0;i<newG[u].size();i++)
 	{
 		int v=newG[u][i];
 		if(!used[v])
 		{
 			used[v]=1;
 			if(match[v]==-1||find(match[v]))
			 {
			 	match[v]=u;
			 	return true;
			 } 
		} 
	}
	return false;
 }                                               
int solve(int n)
{
	int i,res=0;
	time=0;
	cnt=0;
	memset(match,-1,sizeof(match));
	memset(dfn,-1,sizeof(dfn));
 
	for(i=1;i<=n;i++)
	{
		if(dfn[i]==-1)
		{
			tarjan(i);
		}
	}
		suodian(n);
		for(i=1;i<=cnt;i++)
		{
			memset(used,0,sizeof(used));
			bfs(i);                               // 预处理顶点连通性 
		}
 
		for(i=1;i<=cnt;i++)
		{
			memset(used,0,sizeof(used));
			if(find(i))
			{
				res++;
			}
		}
	
	return  cnt-res;
}
int main()
{
	int i,t,n,m,u,v,k=0;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(i=1;i<=n;i++)
		{
			g[i].clear();
			newG[i].clear();
		}
		for(i=0;i<m;i++)
		{
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
		}
		int res=solve(n);
		printf("Case %d: %d
",++k,res);
	}
	return 0;
}

H:LightOJ - 1154 Penguins:题意:有n块浮冰,上面有一定数量的企鹅,企鹅每次跳跃时,会产生冲击力损坏起跳的浮冰,不损坏落下的浮冰,现在告诉你每块浮冰还能承受几次跳跃,问哪些浮冰能够让所有的企鹅聚齐。输入格式:首先一个t,代表测试数据组数,然后n d,代表浮冰块数和企鹅跳跃的最大距离,然后n行,每行 x y a b,分别代表浮冰坐标、浮冰上企鹅数、能承受冲击次数。 思路:首先预处理浮冰间的距离,因为要找到所有的满足条件的浮冰,那么我们枚举每个浮冰为汇点,从0向每个浮冰连边,容量为浮冰上企鹅数,对于每个浮冰,拆点连边,容量为能承受冲击的次数,限制经过的企鹅数。然后浮冰之间距离小于等于给定距离的连边,此时最大流的意义就是有几个企鹅能够到达汇点,如果等于企鹅总数,那就是满足条件的。还有浮冰编号是从0开始的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
 
const int N = 210;
const int INF = 0x3f3f3f3f;
struct edge
{
    int to, cap, next;
}g[N*N*2];
int head[N], iter[N], level[N];
int n, cnt, _case = 0;
void add_edge(int v, int u, int cap)
{
    g[cnt].to = u, g[cnt].cap = cap, g[cnt].next = head[v], head[v] = cnt++;
    g[cnt].to = v, g[cnt].cap = 0, g[cnt].next = head[u], head[u] = cnt++;
}
bool bfs(int s, int t)
{
    memset(level, -1, sizeof level);
    level[s] = 0;
    queue<int> que;
    que.push(s);
    while(! que.empty())
    {
        int v = que.front(); que.pop();
        for(int i = head[v]; i != -1; i = g[i].next)
        {
            int u = g[i].to;
            if(g[i].cap > 0 && level[u] < 0)
            {
                level[u] = level[v] + 1;
                que.push(u);
            }
        }
    }
    return level[t] == -1;
}
int dfs(int v, int t, int f)
{
    if(v == t) return f;
    for(int &i = iter[v]; i != -1; i = g[i].next)
    {
        int u = g[i].to;
        if(g[i].cap > 0 && level[v] < level[u])
        {
            int d = dfs(u, t, min(g[i].cap, f));
            if(d > 0)
            {
                g[i].cap -= d, g[i^1].cap += d;
                return d;
            }
        }
    }
    return 0;
}
int dinic(int s, int t)
{
    int flow = 0, f;
    while(true)
    {
        if(bfs(s, t)) return flow;
        memcpy(iter, head, sizeof head);
        while(f = dfs(s, t, INF),f > 0)
            flow += f;
    }
}
int main()
{
    int t;
    int x[N], y[N], num[N], cut[N];
    double len, dis[N][N];
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%lf", &n, &len);
        int sum = 0;
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d%d%d", x + i, y + i, num + i, cut + i);
            sum += num[i];
        }
        for(int i = 1; i <= n; i++) //预处理浮冰间的距离
            for(int j = i + 1; j <= n; j++)
                dis[i][j] = dis[j][i] = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
        bool flag = false;
        printf("Case %d: ", ++_case);
        for(int i = 1; i <= n; i++) //枚举所有浮冰为汇点
        {
            cnt = 0;
            memset(head, -1, sizeof head);
            for(int j = 1; j <= n; j++)
                add_edge(0, j, num[j]);
            for(int j = 1; j <= n; j++) //拆点
                add_edge(j, n + j, cut[j]);
            for(int j = 1; j <= n; j++)
                for(int k = j + 1; k <= n; k++)
                    if(dis[j][k] <= len)
                    {
                        add_edge(j + n, k, cut[j]); //从j到k,因为拆点的限制,最多能通过cut[j]的流量,设为INF也可以,不容易搞错
                        add_edge(k + n, j, cut[k]); //从k到j,最多通过cut[k]的流量
                    }
            if(dinic(0, i) == sum)
            {
                if(flag) printf(" %d", i - 1);
                else printf("%d", i - 1), flag = true;
            }
        }
        if(! flag) printf("-1");
        printf("
");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/shmilky/p/14089006.html