图论例题合集(二)
目录
A:LightOJ - 1123 Trail Maintenance
C:LightOJ - 1250 Village Postman
E:LightOJ - 1149 Factors and Multiples
G:LightOJ - 1429 Assassin`s Creed (II)
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:\n", cas++);
num = 0;//边数
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &u[num], &v[num], &w[num]);
num++;
printf("%d\n", 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\n",++c,res);
else
printf("Case %d: impossible\n",++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\n",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\n",cas++);
else
printf("Case %d: No\n",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\n",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\n",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\n",++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("\n");
}
return 0;
}