• 图论 | 模板题整理


    // 好些天没有更新博客了,昨天的日记也提到了上个部分的学习难度较大,很多代码并不是自己手写,就没来得及消化。

    // 今天有空就把代码整理一遍,参考的博客地址也都写在了代码前的注释中。

     

    Day 6~7 图论基础


        这天开始引入的例题食物链是一道典型的利用并查集解决的问题:

         题目描述:现在给出三个种族A, B, C,已知它们之间的关系是A吃B B吃C,C吃A,构成了一种奇妙的环状关系。现在给出n个动物构成 的m组信息。信息分为两种,第一种的形式为1 u v,表示u与v为同 类;第二种的形式为2 u v,表示u吃v。试统计有多少组矛盾信息。

         这也是练习题B题 B - 食物链POJ - 1182 

    #include <iostream>
    #include <cstdio>
    using namespace std;
    // https://blog.csdn.net/chaiwenjun000/article/details/50202979
    // https://blog.csdn.net/zcmartin2014214283/article/details/50898722
    const int maxn = 50010;
    int fa[maxn*3];
    int find(int x)
    {
        return x==fa[x]?x:fa[x]=find(fa[x]);
    }
    
    bool same(int x, int y)
    {
        return find(x)==find(y);
    }
    
    void Union(int x, int y)
    {
        int xx = find(x), yy = find(y);
        if(xx==yy)  return;
        
        fa[xx] = yy;
    }
    
    int main()
    {
        int n, k;
        scanf("%d %d", &n, &k);
        for(int i=0;i<=3*n;i++)  fa[i] = i;
        
        int d, x, y, ans = 0;
        for(int i=0;i<k;i++)
        {
            scanf("%d %d %d", &d, &x, &y);
            if(x>n || y>n)
            {
                ans++;
                continue;
            }
            if(d==1)
            {
                if(same(x, y+n)||same(x, y+2*n))  ans++;
                else
                {
                    Union(x, y);
                    Union(x+n, y+n);
                    Union(x+2*n, y+2*n);
                }
            }
            else
            {
                if(same(x, y)||same(x,y+2*n))  ans++;
                else
                {
                    Union(x, y+n);
                    Union(x+n,y+2*n);
                    Union(x+2*n, y);
                }
            }
    
        }
        printf("%d
    ", ans);
        return 0;
    }
    View Code

     

        做练习题时第一题A - How Many Answers Are WrongHDU - 3038 )属于带权并查集,需要在上面find函数上增加一个sum[N]数组来维护区间fa[i]到i之间的和。注意读入的左端点值要减一。

    const int N = 200010;
    int fa[N], sum[N];

    int find(int x)
    {
      if(x==fa[x]) return x;

      int f = find(fa[x]);
      sum[x] += sum[fa[x]];
      return fa[x] = f;
    }

     

        接下来主要讲解了Tarjan算法求强连通分量,这一块我不是很懂,练习题也大都直接略过了。贴一个已解决的E - SPFPOJ - 1523 )求割点的代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    // https://blog.csdn.net/xc19952007/article/details/48349055
    
    #define CLR(a) memset(a, 0, sizeof(a))
    
    const int MAX = 1010;
    int head[MAX], cnt, res[MAX];
    struct Node
    {
        int to, nex;
    }e[100005];
    
    void add(int u, int v)
    {
        ++cnt;
        e[cnt].to = v;
        e[cnt].nex = head[u];
        head[u] = cnt;
    }
    
    int  number[MAX], low[MAX];
    void dfs(int u, int d)
    {
        int cc = 0;
        number[u] = low[u] = d;
        for(int i = head[u];~i;i=e[i].nex)
        {
            int v = e[i].to;
            if(!number[v])
            {
                dfs(v, d+1);
                cc++;
                low[u] = min(low[u], low[v]);
                res[u] += u==1?cc>1:low[v]>=number[u];
            }
            else
                low[u] = min(low[u], number[v]);
    
        }
    
    }
    
    
    void fun()
    {
        bool flag = 0;; 
        for(int i=1;i<1010;i++)
        if(res[i])
        {
            printf("  SPF node %d leaves %d subnets
    ", i, res[i]+1);
            flag = 1;
        }
        if(!flag) printf("  No SPF nodes
    ");
        
    } 
     
    void Init()
    {
    //    memset(e, 0, sizeof(e));
        memset(head, -1, sizeof(head));
    //    CLR(e);
        CLR(number);
        CLR(low);
        CLR(res);
        cnt = 0;
    } 
     
    int main()
    {
        int u, v, t = 0;
        while(scanf("%d", &u)==1 && u)
        {
            scanf("%d", &v);
            Init();
            add(u, v); add(v, u);
    
            while(scanf("%d", &u)==1 && u)
            {
                scanf("%d", &v);
                add(u, v);  add(v, u);
    
            }
            dfs(1, 1);  
            printf("Network #%d
    ", ++t);
            fun();
            printf("
    ");
        }
        
        return 0;
    }
    View Code

     

        后来补的H - Highways( POJ - 1751 )这是一个最小生成树的模板题,也就是Day 8的主要内容,采用的Prim算法求解最小生成树。

    #include<iostream>
    #include<algorithm>
    #include<stdio.h>
    #include<string.h>
    #include<math.h>
    using namespace std;
    //普利姆,注意建图技巧
    const int maxn=751;
    const int INF=0x3f3f3f3f;
    int map[maxn][maxn];
    int dis[maxn];
    int vis[maxn];
    int Edge[maxn];//i到Edge[i]是一条生成树内的边
    struct node
    {
        int x;
        int y;
    } Point[maxn]; //第i个点的坐标
    int N;//点的数量
    int M;//更新边的数量
    void init()
    {
        scanf("%d",&N);
        for(int i=1; i<=N; i++)//建图
        {
            scanf("%d%d",&Point[i].x,&Point[i].y);
            for(int j=1; j<i; j++)//为什么这里不取sqrt,因为完全没必要
                map[i][j]=map[j][i]=(Point[i].x-Point[j].x)*(Point[i].x-Point[j].x)+(Point[i].y-Point[j].y)*(Point[i].y-Point[j].y);
            map[i][i]=INF;//自己不可能到自己
        }
        scanf("%d",&M);
        int x,y;
        while(M--)//更新图
        {
            scanf("%d%d",&x,&y);
            map[x][y]=map[y][x]=0;
        }
        memset(vis,0,sizeof(vis));
        vis[1]=1;
        for(int i=1; i<=N; i++)
        {
            dis[i]=map[i][1];
            Edge[i]=1;//初始化为存储i到1的边
        }
    }
    void Prim()
    {
        for(int i=1; i<N; i++)
        {
            int minn=INF;
            int point_minn;
            for(int j=1; j<=N; j++)
                if(vis[j]==0&&minn>dis[j])
                {
                    minn=dis[j];
                    point_minn=j;
                }
            vis[point_minn]=1;
            for(int k=1; k<=N; k++)
                if(vis[k]==0&&dis[k]>map[point_minn][k])
                {
                    Edge[k]=point_minn;//这里是输出方式的技巧
                    dis[k]=map[point_minn][k];
                }
            if(map[Edge[point_minn]][point_minn])
                printf("%d %d
    ",Edge[point_minn],point_minn);
        }
    }
    int main()
    {
        init();
        Prim();
        return 0;
    }
    View Code

     

    Day 8 生成树相关扩展

    相关问题有以下内容:

    • 次小生成树
    • 最小度限制生成树
    • 最优比率生成树
    • 最小树形图

        似乎除了最优比率生成树以外我都没掌握,下面是由01分数规划过渡到最优比率生成树的问题题解。

     01分数规划就是给定两个数组,a[i]表示选取i的收益,b[i]表示选取i的代价,求一个选取方案 使  取得最优解。解决思路就是二分搜索

        B - Dropping tests( POJ - 2976 

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    int a[1010], b[1010];
    double c[1010];
    bool cmp(double a, double b)
    {
        return a>b;
    }
    
    int main()
    {
        int n, k;
        while(scanf("%d %d", &n, &k)!=EOF && n)
        {
            for(int i=0;i<n;i++)
                scanf("%d", &a[i]);
            for(int i=0;i<n;i++)
                scanf("%d", &b[i]);
            double l = 0, r = 1, mid = 0.5;
            while(r-l>=0.0001)  // WA: 0.001
            {
                mid = (l+r)/2;
                for(int i=0;i<n;i++)
                    c[i] = a[i] - mid*b[i];
                sort(c, c+n, cmp);
                bool flag = 1;
                double sum = 0;
                for(int i=0;i<n-k;i++)
                {
                    sum += c[i];
                    if(sum<0)  {flag = 0; break;}
                }
    //            cout<<flag<<' '<<mid<<endl;
                if(flag)  l = mid;
                else  r = mid;
            }
            printf("%1.f
    ", 100*l);  // OR: %d,(int)(100*l+0.5)
        }
    
        return 0;
    }
    View Code

        

    最优比率生成树:

        C - Desert King( POJ - 2728 

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    using namespace std;
    
    // https://blog.csdn.net/chenzhenyu123456/article/details/48160209
    
    int n;
    struct node{
        int x, y, h;
    }P[1010];
    
    double map[1010][1010], len[1010][1010], cost[1010][1010];
    double low[1010], Max;
    bool vis[1010];
    
    
    double Dis(double x, double y)
    {
        return sqrt(x*x+y*y);
    }
    
    double prim()
    {
        double res = 0;
        for(int i=1;i<=n;i++)
        {
            vis[i] = 0;
            low[i] = map[i][1];
        }
        vis[1] = 1;
    
        for(int i=1;i<n;i++)
        {
            double minn = 20000000;
            int nxt, flag = 1;
            for(int j=1;j<=n;j++)
            {
                if(!vis[j] && minn>low[j])
                {
                    minn = low[j];
                    nxt = j;
                    flag = 0;
                }
            }
    
            if(flag)  break;
            res += minn;
            vis[nxt] = 1;
    
            for(int j=1;j<=n;j++)
            {
                if(!vis[j] && low[j]>map[nxt][j])
                    low[j] = map[nxt][j];
                
            }
        }
    //    printf("%lf	", res);
        return res;
    }
    
    void init()
    {
        Max = 0;
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            {
                cost[i][j] = cost[j][i] = abs(P[i].h-P[j].h);
                Max = max(Max, cost[i][j]/len[i][j]);
            }
    }
    
    bool judge(double k)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
                map[i][j] = map[j][i] = cost[i][j] - k*len[i][j];
        }
        if(prim()>=0) return true;
        return false;
    }
    
    int main()
    {
        
        while(scanf("%d", &n)!=EOF && n)
        {
            for(int i=1;i<=n;i++)
            {
                scanf("%d %d %d", &P[i].x, &P[i].y, &P[i].h);
                for(int j=1;j<i;j++)
                    len[i][j] = len[j][i] = Dis(P[i].x-P[j].x, P[i].y-P[j].y);
    
            }
            init();
    //        for(int i=1;i<=n;i++,cout<<endl)
    //        for(int j=i+1;j<=n;j++)
    //        printf("(%lf %lf)", len[i][j], cost[i][j]); 
            double l = 0, r = Max, mid;
            while(r-l>=1e-6) 
            {
                mid = (l+r)/2;
    //            printf("%lf
    ", mid);
                if(judge(mid))  l = mid;
                else  r = mid;
            }
            printf("%.3lf
    ", l);  
        }
    
        return 0;
    }
    View Code

          然后解(copy)决(code)了好几个最小点覆盖问题,这里直接先上结论:对于二分图,1.最小点覆盖=最大匹配 2.最大独立集=点的个数-最小点覆盖。

      因此求最小点覆盖数也就是求最大匹配数,最大独立集也都能用相同的算法求解。下面的代码采用了匈牙利算法

       G - Machine Schedule( POJ - 1325 

    /*
    顶点编号从0开始的
    邻接矩阵(匈牙利算法)
    二分图匹配(匈牙利算法的DFS实现)(邻接矩阵形式)
    初始化:g[][]两边顶点的划分情况
    建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
    g没有边相连则初始化为0
    uN是匹配左边的顶点数,vN是匹配右边的顶点数
    左边是X集,右边是Y集
    调用:res=hungary();输出最大匹配数
    优点:适用于稠密图,DFS找增广路,实现简洁易于理解
    时间复杂度:O(VE)
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int MAXN=110;
    int uN,vN;//u,v 的数目,使用前面必须赋值
    int g[MAXN][MAXN];//邻接矩阵,记得初始化
    int linker[MAXN];//linker[v]=u,表示v(右边Y集合中的点)连接到u(左边X集合中的点)
    bool used[MAXN];
    bool dfs(int u)
    {//判断以X集合中的节点u为起点的增广路径是否存在
        for(int v=0;v<vN;v++)//枚举右边Y集合中的点
            if(g[u][v]&&!used[v]){//搜索Y集合中所有与u相连的未访问点v
                used[v]=true;//访问节点v
                if(linker[v]==-1||dfs(linker[v])){//是否存在增广路径
                    //若v是未盖点(linker[v]==-1表示没有与v相连的点,即v是未盖点),找到增广路径
                    //或者存在从与v相连的匹配点linker[v]出发的增广路径
                    linker[v]=u;//设定(u,v)为匹配边,v连接到u
                    return true;//返回找到增广路径
                }
            }
            return false;
    }
    int hungary()
    {//返回最大匹配数(即最多的匹配边的条数)
        int res=0;//最大匹配数
        memset(linker,-1,sizeof(linker));//匹配边集初始化为空
        for(int u=0;u<uN;u++){//找X集合中的点的增广路
            memset(used,false,sizeof(used));//设Y集合中的所有节点的未访问标志
            if(dfs(u))res++;//找到增广路,匹配数(即匹配边的条数)+1
        }
        return res;
    }
    
    int main()
    {
        int i, ans;
        int K;
    
        int p, R, C;
        while(~scanf("%d",&uN) && uN)
        {
            scanf("%d %d", &vN, &K);
            memset(g,0,sizeof(g));
            while(K--)
            {
                scanf("%d%d%d",&p,&R,&C);
                if(R>0&&C>0) g[R][C]=1;
            }
            ans = hungary();
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

      

          当我一直用上个模板到Day 11的dp进阶专题时,有道题P - Strategic game( POJ - 1463 出现了TLE,上网一查发现用邻接矩阵的匈牙利算法效率会有些低,所以又抄了一份邻接表的写法:

    /*
    HDU 1054
    用STL中的vector建立邻接表实现匈牙利算法
    效率比较高
    
     
    */
    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    using namespace std;
    // https://www.cnblogs.com/kuangbin/archive/2012/08/19/2646713.html
    //************************************************
    const int MAXN=1505;
    int linker[MAXN];
    bool used[MAXN];
    vector<int>map[MAXN];
    int uN;
    bool dfs(int u)
    {
        for(int i=0;i<map[u].size();i++)
        {
            if(!used[map[u][i]])
            {
                used[map[u][i]]=true;
                if(linker[map[u][i]]==-1||dfs(linker[map[u][i]]))
                {
                    linker[map[u][i]]=u;
                    return true;
                }
            }
        }
        return false;
    }
    int hungary()
    {
        int u;
        int res=0;
        memset(linker,-1,sizeof(linker));
        for(u=0;u<uN;u++)
        {
            memset(used,false,sizeof(used));
            if(dfs(u)) res++;
        }
        return res;
    }
    //*****************************************************
    int main()
    {
        int u,k,v;
        int n;
        while(scanf("%d",&n)!=EOF)
        {
            for(int i=0;i<MAXN;i++)
               map[i].clear();
            for(int i=0;i<n;i++)
            {
                scanf("%d:(%d)",&u,&k);
                while(k--)
                {
                    scanf("%d",&v);
                    map[u].push_back(v);
                    map[v].push_back(u);
                }
            }
            uN=n;
            printf("%d
    ",hungary()/2);
        }
        return 0;
    }
    View Code

    //Day 9 网络流

      // 这天一直在补前面的生成树的题,练习题迟迟没做以致最后爆零T.T

      // 这块真的要等到学好了图论基础再来,目前只知道两个基本概念——最大流和最小割,什么增广路算法和Dinic算法有待后续理解和掌握0.0

         未完待续......

  • 相关阅读:
    我的本科毕业论文——Messar即时通讯系统
    你为什么不用Flash做程序的表示层呢?
    用于Blog的天气预报服务-改进20050806
    写了个小程序,方便大家编程(QuickDog,快捷键帮手)
    庆祝"上海.NET俱乐部"今天成立,请申请加入的朋友在这里Sign you name
    HTML+CSS+Javascript教学视频【0409更新】
    关于推迟7月9日上海.NET俱乐部第一次技术交流会的通知
    关于“上海.NET俱乐部”第一次技术交流会进展报告
    2005年8月13日 上海.NET俱乐部第一次活动纪实 已经发布,资料提供下载
    喜欢互联网行业,是因为它拥有着无穷的变数
  • 原文地址:https://www.cnblogs.com/izcat/p/9484833.html
Copyright © 2020-2023  润新知