图论例题合集(一)

mac2024-07-23  55

目录

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\n"); printf("%d",cont); for(int i=0;i<cont;i++) { printf(" %d",output[i]); } printf("\n"); } else printf("No\n"); } }

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

#include<set> #include<stack> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define endl '\n' 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\n",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\n", 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\n"); break; } } if (flag) printf("NO\n"); } 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:\n", cas++); spfa(x); for (int i = 0; i < n; i++) { if(dis[i]>=INF) printf("Impossible\n"); else printf("%d\n", 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\n",MIN,MAX); printf ("Case %d: ",cou); cou++; if ((MIN + MAX) % 2 == 0) printf ("%d\n",(MIN + MAX)/2); else printf ("%d/2\n",MAX+MIN); } return 0; }

 

最新回复(0)