图论 | 模板题整理

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

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

 

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\n", 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\n", i, res[i]+1);
        flag = 1;
    }
    if(!flag) printf("  No SPF nodes\n");
    
} 
 
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\n", ++t);
        fun();
        printf("\n");
    }
    
    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\n",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\n", 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\t", 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\n", mid);
            if(judge(mid))  l = mid;
            else  r = mid;
        }
        printf("%.3lf\n", 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\n",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\n",hungary()/2);
    }
    return 0;
}
View Code

 

//Day 9 网络流

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

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

 

     未完待续......

posted @ 2018-08-16 00:23  izcat  阅读(473)  评论(0编辑  收藏  举报