图论例题合集(一)

目录

A:LightOJ - 1251 Forming the Council

B:LightOJ - 1063 Ant Hills

C:LightOJ - 1291 Real Life Traffic

D:LightOJ - 1074 Extended Traffic

E:LightOJ - 1108 Instant View of Big Bang

F:LightOJ - 1221 Travel Company

G:LightOJ - 1002 Country Roads

H:LightOJ - 1029 Civil and Evil Engineer


A:LightOJ - 1251 Forming the Council:题目大意:一共有N个选民,M个参选者,一个选民有两个意向,对于+ 表示希望这个人当选。 - 表示希望这个人落选,至少满足一条的结果,对于这个选民来讲就是满足的。问是否有一个可行解,使得所有选民都满足。如果有,输出当选的人的编号。思路:很明显的2-Sat问题。首先我们将一个参选者的当选和不当选进行拆点。i代表第i个人当选了,i+n代表这个人没有当选。那么这个问题就变成了一个经典的2-Sat问题。

#include<stdio.h>
#include<string.h>
#include<vector>
#include<queue>
using namespace std;
int output[40005];
int vis[70005];
int low[70005];
int dfn[70005];
int print[70005];
int stack[70005];
int color[70005];
int pos[70005];
int degree[70005];
vector<int >mp[70005];
vector<int >mp2[70005];
int n,m,sig,cnt,tot,cont;
void add(int x,int y)
{
    mp[x].push_back(y);
}
void top()
{
    memset(print,0,sizeof(print));
    queue<int >s;
    for(int i=1;i<=sig;i++)
    {
        if(degree[i]==0)
        {
            s.push(i);
        }
    }
    while(!s.empty())
    {
        int u=s.front();
        if(print[u]==0)
        {
            print[u]=1;print[pos[u]]=2;
        }
        s.pop();
        for(int i=0;i<mp2[u].size();i++)
        {
            int v=mp2[u][i];
            degree[v]--;
            if(degree[v]==0)s.push(v);
        }
    }
    cont=0;
    for(int i=1;i<=n;i++)if(print[color[i]]==1)output[cont++]=i;
}
void Tarjan(int u)
{
    vis[u]=1;
    dfn[u]=low[u]=cnt++;
    stack[++tot]=u;
    for(int i=0;i<mp[u].size();i++)
    {
        int v=mp[u][i];
        if(vis[v]==0)Tarjan(v);
        if(vis[v]==1)low[u]=min(low[u],low[v]);
    }
    if(low[u]==dfn[u])
    {
        sig++;
        do
        {
            vis[stack[tot]]=-1;
            color[stack[tot]]=sig;
        }
        while(stack[tot--]!=u);
    }
}
int Slove()
{
    sig=0;
    cnt=1;
    tot=-1;
    memset(degree,0,sizeof(degree));
    memset(stack,0,sizeof(stack));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(color,0,sizeof(color));
    for(int i=1;i<=n*2;i++)
    {
        if(vis[i]==0)
        {
            Tarjan(i);
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(color[i]==color[i+n])return 0;
        pos[color[i]]=color[i+n];
        pos[color[i+n]]=color[i];
    }
    for(int i=1;i<=n*2;i++)
    {
        for(int j=0;j<mp[i].size();j++)
        {
            int v=mp[i][j];
            if(color[i]!=color[v])
            {
                degree[color[i]]++;
                mp2[color[v]].push_back(color[i]);
            }
        }
    }
    top();
    return 1;
}
int main()
{
    int t;
    int kase=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&m,&n);
        for(int i=1;i<=60000;i++)mp[i].clear(),mp2[i].clear();
        for(int i=0;i<m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            int xx=x;int yy=y;
            if(x<0)x=-x;
            if(y<0)y=-y;
            if(xx>0&&yy>0)add(x+n,y),add(y+n,x);
            if(xx>0&&yy<0)add(x+n,y+n),add(y,x);
            if(xx<0&&yy>0)add(x,y),add(y+n,x+n);
            if(xx<0&&yy<0)add(x,y+n),add(y,x+n);
        }
        int ans=Slove();
        printf("Case %d: ",++kase);
        if(ans==1)
        {
            printf("Yes
");
            printf("%d",cont);
            for(int i=0;i<cont;i++)
            {
                printf(" %d",output[i]);
            }
            printf("
");
        }
        else printf("No
");
    }
}

B:LightOJ - 1063 Ant Hills:题目大意:给你n个点,m条边,然后让你破坏某个点使得至少其他两个点不连通,就是用强连通求割点个数。思路:求割点的模板。

#include<set>
#include<stack>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define endl '
'
using namespace std;
 
const int maxn = 10010;
 
vector<int> v[maxn];
stack<int> s;
set<int> cut;
int low[maxn],DFN[maxn],vis[maxn];
int Belong[maxn];//Belong数组的值是1~scc
int num[maxn];//各个强连通分量包含点的个数,数组编号1~scc
int id;
int scc;//强连通分量的个数
void Tarjan(int x,int fa)
{
    low[x]  = DFN[x] = ++id;
    vis[x] = 1;
    s.push(x);
    int son = 0;
    for(int i=0;i<v[x].size();i++)
    {
        int xx = v[x][i];
        if(!DFN[xx])
        {
            Tarjan(xx,x);
            low[x]=min(low[x],low[xx]);
            //求割点
            if (fa == -1 && son != 0)
                cut.insert(x);
            if (fa != -1 && low[xx] >= DFN[x])
                cut.insert(x);
            son++;
        }
        else if(vis[xx])
            low[x]=min(low[x],DFN[xx]);
    }
    if(low[x]==DFN[x])
    {
        scc++;
        while(s.size())
        {
            int xx=s.top();
            s.pop();
            num[scc]++;
            Belong[xx] = scc;
            vis[xx] = 0;
            if(xx==x)
                break;
        }
    }
}
void solve(int n)
{
    for(int i = 1;i <= n;i++)
        if(!DFN[i])
            Tarjan(i,-1);
}
void init()
{
    memset(DFN,0,sizeof(DFN));
    memset(num,0,sizeof(num));
    memset(vis,0,sizeof(vis));
    while(s.size())
        s.pop();
    cut.clear();
    for(int i=0;i<=maxn;i++)
        v[i].clear();
    scc = id = 0;
}
int main(void)
{
    int T,n,m;
    scanf("%d",&T);
    int cas = 1;
    while(T--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        solve(n);
        printf("Case %d: %d
",cas++,cut.size());
    }
    return 0;
}

C:LightOJ - 1291 Real Life Traffic:题意:最少添加几条边 可以使全图边双连通。思路:简单的缩点问题。通过Tarjian求出各个双连通分支后缩点,统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
 

#include <vector>
#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
const int maxn = 10010;
vector <int> a[maxn];
int pre[maxn];
int low[maxn];
int bccno[maxn];
int dfs_clock;
int bcc_cnt;
int bri;
int n, m;
int degree[maxn];
stack <int> S;

void dfs(int u, int fa)
{
	low[u] = pre[u] = ++dfs_clock;
	S.push(u);
	for (int i = 0; i < a[u].size(); i++)
	{
		int v = a[u][i];
		if (v == fa)
			continue;
		if (!pre[v])
		{
			dfs(v, u);
			low[u] = min(low[u], low[v]);
		}
		else if (v != fa)
			low[u] = min(low[u], pre[v]);
	}
	if (low[u] == pre[u])
	{
		bcc_cnt++;
		while (1)
		{
			int x = S.top(); S.pop();
			bccno[x] = bcc_cnt;
			if (x == u)
				break;
		}
	}
}
void find_bcc()
{
	memset(pre, 0, sizeof(pre));
	memset(bccno, 0, sizeof(bccno));
	dfs_clock = bcc_cnt = bri = 0;
	for (int i = 0; i < n; i++)
		if (!pre[i])
			dfs(i, -1);
}
int main()
{
	int cas = 1;
	int T;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d %d", &n, &m);
		for (int i = 0; i <= n; i++)
			a[i].clear();
		while (m--)
		{
			int u, v;
			scanf("%d %d", &u, &v);
			a[u].push_back(v);
			a[v].push_back(u);
		}

		find_bcc();
		memset(degree, 0, sizeof(degree));
		for (int u = 0; u < n; u++)
		{
			for (int i = 0; i < a[u].size(); i++)
			{
				int v = a[u][i];
				if (bccno[u] != bccno[v])
				{
					degree[bccno[u]]++;
					degree[bccno[v]]++;
				}
			}
		}
		int ans = 0;
		for (int i = 1; i <= bcc_cnt; i++)
			if (degree[i] == 2)
				ans++;
		printf("Case %d: %d
", cas++, (ans + 1) / 2);
	}
	return 0;
}

D:LightOJ - 1074 Extended Traffic:题目大意:就是有n个点,1--n的顺序给出每个点都有一定的拥挤值,然后给你m条边,这m条边表示相连着,着m条边的边权值是目的地点的值减去出发点的值的3次方,注意,这是有向图,然后再给你q个点,去求1号点到这q个点的最小值,如果值小于3,或者不能够到达,那就输出 ‘?’思路:可以用spfa算法判断负环。SPFA就是Bellman 的队列优化,所以每个点最多进入到队列中n次就可以找到最短路,如果是进入到队列里面大于等于n次就说明存在负权回路,在这道题目中,负权回路上的所有的点都是不满足题意的,所以就输出  "?"。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 205;
int cnt;
int t, n, m;
typedef struct {
	int nxt, to, w;
}edge;
int head[maxn], num[maxn], dis[maxn], cir[maxn];
edge edges[maxn * 100];
int v[maxn], vis[maxn];
void add(int from, int to, int w) {
	edges[++cnt].to = to;
	edges[cnt].w = w;
	edges[cnt].nxt = head[from];
	head[from] = cnt;
}
void dfs(int id) {
	cir[id] = true;
	for (int i = head[id]; i; i = edges[i].nxt) {
		if (!cir[edges[i].to])
			dfs(edges[i].to);
	}
}
void spfa(int s) {
	queue<int>st;
	st.push(s);
	num[1] = 1; vis[1] = 1; dis[1] = 0;
	while (!st.empty()) {
		int cur = st.front(); st.pop();
		vis[cur] = false;
		if (cir[cur])continue;
		for (int i = head[cur]; i; i = edges[i].nxt) {
			int v = edges[i].to;
			if (dis[v] > edges[i].w + dis[cur]) {
				dis[v] = edges[i].w + dis[cur];
				if (!vis[v]) {
					st.push(v);
					vis[v] = true;
					num[v]++;
					if (num[v] == n) {
						dfs(v);
					}
				}
			}
		}
	}
}
void init() {
	memset(dis, inf, sizeof(dis));
	memset(head, 0, sizeof(head));
	memset(num, 0, sizeof(num));
	memset(vis, 0, sizeof(vis));
	memset(cir, 0, sizeof(cir));
	cnt = 0;
}
int main() {
	cin >> t;
	int x, y;
	int qt = 1;
	while (t--) {
		init();
		cin >> n;
		for (int i = 1; i <= n; i++) {
			cin >> v[i];
		}
		cin >> m;
		for (int i = 1; i <= m; i++) {
			cin >> x >> y;
			add(x, y, ((v[y] - v[x]) * (v[y] - v[x]) * (v[y] - v[x])));
		}
		spfa(1);
		cout << "Case " << qt++ << ":" << endl;
		int ca, query;
		cin >> ca;
		for (int i = 1; i <= ca; i++) {
			cin >> query;
			if (dis[query] >= inf || dis[query] < 3 || cir[query]) {
				cout << "?" << endl;
			}
			else {
				cout << dis[query] << endl;
			}
		}
	}
}

E:LightOJ - 1108 Instant View of Big Bang:题意:求哪些点可以回到过去 首先负环的点是可以的 一直在付欢里转即可 然后那些可以走到负环的点满足。思路:反向建图 这样负环还是不会变的 只不过负环的方向换了下 原来能到负环的点变成了现在负环能到的点 求出负环标记然后广搜负环能到的点再标记。

//注意没说起点,所以spfa中dis数组的初始化全为零并把所有点放到队列里
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2050;
int cnt;
int t, n, m;
typedef struct {
	int nxt, to, w;
}edge;
int head[maxn], num[maxn], dis[maxn], cir[maxn];
edge edges[maxn * 100];
int v[maxn], vis[maxn];
void add(int from, int to, int w) {
	edges[++cnt].to = to;
	edges[cnt].w = w;
	edges[cnt].nxt = head[from];
	head[from] = cnt;
}
void dfs(int id) {
	cir[id] = true;
	for (int i = head[id]; i; i = edges[i].nxt) {
		if (!cir[edges[i].to])
    		dfs(edges[i].to);
	}
}
void spfa(int s) {
	queue<int>st;
	st.push(s);
    dis[0] = 0;
	for (int i = 0; i < n; i++)
	{
		dis[i] = 0;
		st.push(i);
	}
	while (!st.empty()) {
		int cur = st.front(); st.pop();
		vis[cur] = false;
		if (cir[cur])continue;
		for (int i = head[cur]; i; i = edges[i].nxt) {
			int v = edges[i].to;
			if (dis[v] > edges[i].w + dis[cur]) {
				dis[v] = edges[i].w + dis[cur];
				if (!vis[v]) {
					st.push(v);
					vis[v] = true;
					num[v]++;
					if (num[v] >= n) {
						dfs(v);
					}
				}
			}
		}
	}
}
void init() {
	memset(dis, 0, sizeof(dis));
	memset(head, 0, sizeof(head));
	memset(num, 0, sizeof(num));
	memset(vis, 0, sizeof(vis));
	memset(cir, 0, sizeof(cir));
	cnt = 0;
}
int main() {
	cin >> t;
	int x, y, z;
	int qt = 1;
	while (t--) {
		init();
		cin >> n >> m;
		for (int i = 1; i <= m; i++) {
			cin >> x >> y >> z;
			add(y, x, z);
		}
		spfa(0);
		cout << "Case " << qt++ << ":" ;
		int flag = 0;
		for (int i = 0; i < n; i++) {
			if (cir[i]) {
				cout << ' ' << i;
				flag = 1;
			}
		}
		
		if (flag)
			cout << endl;
		else
			cout << " impossible" << endl;
	}
}

F:LightOJ - 1221 Travel Company:题目: 旅游公司推出了一种新的旅游路线,这里有n个城市,编号为0~n-1,有m条路线,每行输入u v in out 4个变量,u v 代表编号为u和v的两个城市相连,in代表旅游公司在这条路线上的收入,out代表支出,最开始一行给出一个利润率p(收入/支出),公司要求找到一条循环路线(即一个环),使得总利率大于p。
思路: 我们可以根据题意写出这么一条原始的公式:(in[0]+in[1]+...+in[k])/(out[0]+out[1]+...+out[k])>p
即p*(out[0]+out[1]+...+out[k])<(in[0]+in[1]+....in[k]), 故,就是对于这个环中的任意一条边,都要求p*out[i]-in[i]<0,即在图中存在负环。用Spfa算法,因为最短路最多对边松弛n-1次就可以求出来,那么如果一个点入队次数等于n,说明存在负环,即不存在最短路。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <list>
using namespace std;
const  int maxn = 50100;
const int INF = 0x3f3f3f3f;
int n, m, dis[maxn], vis[maxn], intime[maxn];
vector < pair<int, int> > eg[maxn];
int spfa(int src)
{
	queue <int> Q;
	memset(dis, INF, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	memset(intime, 0, sizeof(intime));
	dis[src] = 0;
	Q.push(src);
	while (!Q.empty()){
		int u = Q.front(); Q.pop();
		vis[u] = 0; intime[u]++;
		if (intime[u] > n)
			return 1;
		int len = eg[u].size();
		for (int i = 0; i < len; i++){
			int v = eg[u][i].first;
			int w = eg[u][i].second;
			if (dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				if (!vis[v]){
					vis[v] = 1;
					Q.push(v);
				}
			}
		}
	}
	return 0;
}
int main()
{
	int T, p, cas = 1;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d%d", &n, &m, &p);
		for (int i = 0; i <= n; i++)
			eg[i].clear();
		while (m--){
			int u, v, in, out;
			scanf("%d%d%d%d", &u, &v, &in, &out);
			int tem = out * p - in;
			eg[u].push_back(make_pair(v, tem));
		}
		printf("Case %d: ", cas++);
		int flag = 1;
		for (int i = 0; i < n; i++){
			if (spfa(i)){
				flag = 0;
				printf("YES
");
				break;
			}
		}
		if (flag)
			printf("NO
");
	}
	return 0;
}

G:LightOJ - 1002 Country Roads:题目:一共n个城市 ,m 条路, 最后还有一个城市编号 t, 求从t 城市到所有城市的每一条路径中最长边中最小的那条边, 如果到不了 ,输出"Impossible"。思路:具体描述可看https://blog.csdn.net/qq_44765711/article/details/100526905

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <list>
using namespace std;
const  int maxn = 50100;
const int INF = 0x3f3f3f3f;
int n, m, x, dis[maxn], vis[maxn], intime[maxn];
vector < pair<int, int> > eg[maxn];
void spfa(int src)
{
	queue <int> Q;
	memset(dis, INF, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	dis[src] = 0;
	Q.push(src);
	while (!Q.empty()){
		int u = Q.front(); Q.pop();
		vis[u] = 0;
		int len = eg[u].size();
		for (int i = 0; i < len; i++){
			int v = eg[u][i].first;
			int w = eg[u][i].second;
			int tmp = max(dis[u], w);
			if (dis[v] >  tmp){
				dis[v] =  tmp;
				if (!vis[v]){
					vis[v] = 1;
					Q.push(v);
				}
			}
		}
	}
}
int main()
{
	int T, cas = 1;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		for (int i = 0; i <= n; i++)
			eg[i].clear();
		while (m--){
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			eg[u].push_back(make_pair(v, w));
			eg[v].push_back(make_pair(u, w));
		}
		scanf("%d", &x);
		printf("Case %d:
", cas++);
		spfa(x);
		for (int i = 0; i < n; i++) {
			if(dis[i]>=INF)
				printf("Impossible
");
			else
				printf("%d
", dis[i]);
		}
	}
	return 0;
}

H:LightOJ - 1029 Civil and Evil Engineer:求最小生成树和最大生成树

#include <stdio.h>
#include <iostream>
#include <string.h>
const int inf = 1e6;
using namespace std;
 
int map[1000][1000],Map[1000][1000];
int dis[10000],vist[10000];
int MIN = 0,MAX = 0;
int x;
 
void minn()
{
    for(int i = 0; i<=x; i++)
    {
        vist[i] = 0;
        dis[i] = map[0][i];
    }
 
    vist[0] = 1 ;
    for(int i = 1; i<=x; i++)
    {
        int min = inf,pos = 0;
        for(int j = 0; j<=x; j++)
        {
            if(vist[j] == 0 && dis[j] < min)
            {
                min = dis[j];
                pos = j;
            }
        }
 
        MIN += min;
        vist[pos] = 1 ;
        for(int j = 0; j<=x; j++)
        {
            if(vist[j] == 0 && dis[j] > map[pos][j])
            {
                dis[j] = map[pos][j];
            }
        }
    }
}
void maxx()
{
    for(int i = 0; i<=x; i++)
    {
        vist[i] = 0;
        dis[i] = Map[0][i];
    }
 
    vist[0] = 1 ;
    for(int i = 1; i<=x; i++)
    {
        int min = 0,pos = 0;
        for(int j = 0; j<=x; j++)
        {
            if(!vist[j] && dis[j] > min)
            {
                min = dis[j];
                pos = j;
            }
        }
        if(pos==0)
            break;
        MAX += min;
        vist[pos] = 1 ;
        for(int j = 0; j<=x; j++)
        {
            if(vist[j]  == 0&& dis[j] < Map[pos][j])
            {
                dis[j] = Map[pos][j];
            }
        }
    }
}
int main()
{
    int a,b,c,t;
    int cou = 1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&x);
        memset(Map,0,sizeof(Map));
        memset(map,inf,sizeof (map));
        while(~scanf("%d%d%d",&a,&b,&c))
        {
            if(a==0 &&b==0 &&c==0)
                break;
            if(c<map[a][b])
                map[a][b] = map[b][a] = c;
            if(c>Map[a][b])
                Map[a][b] = Map[b][a] = c;
        }
        MIN = 0;
        MAX = 0;
 
        minn ();
        maxx ();
 
        // printf ("%d %d
",MIN,MAX);
        printf ("Case %d: ",cou);
        cou++;
        if ((MIN + MAX) % 2 == 0)
            printf ("%d
",(MIN + MAX)/2);
        else printf ("%d/2
",MAX+MIN);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/shmilky/p/14089007.html