zoukankan      html  css  js  c++  java
  • 图论例题合集(二)

    目录

     

    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;
    }
  • 相关阅读:
    auto_ptr智能指针
    友元函数
    设计模式之单件模式
    vs2008工程部署不成功,可能是远程文件路径出现问题
    printf(“%06d ”,x);
    16进制到文本字符串的转换,在线实时转换
    文件包含
    pwn-200
    misc-3-1
    misc-适合作为桌面
  • 原文地址:https://www.cnblogs.com/shmilky/p/14089006.html
Copyright © 2011-2022 走看看