zoukankan      html  css  js  c++  java
  • 【网络流24题】

    算法:网络流

    题目:网络流24题(多按一下F5)

    最小路径覆盖问题

    关于输出路径,因为即使有反向弧经过左侧点也一定会改变左侧点的去向,若没连向右侧就会被更新到0,所以不用在意。

    mark记录有入度的右侧点,然后从没入度的右侧点开始把整条路径输出来即可。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn=100000,inf=0x3f3f3f3f;
    int n,m,S,T,d[maxn],q[10010],first[maxn],tot=1,nex[maxn];
    bool mark[maxn];
    struct edge{int from,v,flow;}e[maxn];
    void insert(int u,int v,int w)
    {tot++;e[tot].v=v;e[tot].flow=w;e[tot].from=first[u];first[u]=tot;
     tot++;e[tot].v=u;e[tot].flow=0;e[tot].from=first[v];first[v]=tot;}
    bool bfs()
    {
        memset(d,-1,4*(2*n+3));
        int head=0,tail=1;q[0]=S;d[S]=0;
        while(head<tail)
         {
             int x=q[head++];if(head>10000)head=0;
             for(int i=first[x];i;i=e[i].from)
              if(e[i].flow&&d[e[i].v]==-1)
               {
                   int y=e[i].v;
                   d[y]=d[x]+1;
                   q[tail++]=y;if(tail>10000)tail=0;
               }
         }
        return d[T]!=-1;
    }
    int dinic(int x,int a)
    {
    //    printf("x=%d a=%d
    ",x,a);
        if(x==T||a==0)return a;
        int flow=0,f;
        for(int i=first[x];i;i=e[i].from)
         if(e[i].flow&&d[e[i].v]==d[x]+1&&(f=dinic(e[i].v,min(a,e[i].flow)))>0)
          {
              if(f>0)
             {
                 nex[x]=e[i].v;
                 if(e[i].v-n>0)mark[e[i].v-n]=1;
             }
              e[i].flow-=f;
              e[i^1].flow+=f;
              a-=f;
              flow+=f;
              if(a==0)break;
          }
        return flow;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        S=0,T=2*n+1;
        for(int i=1;i<=m;i++)
         {
             int u,v;
             scanf("%d%d",&u,&v);
             insert(u,v+n,1);
         }
        for(int i=1;i<=n;i++)insert(S,i,1);
        for(int i=n+1;i<=2*n;i++)insert(i,T,1);
        int ans=n;
        while(bfs())ans-=dinic(S,inf);
        for(int i=1;i<=n;i++)if(!mark[i])
         {
             printf("%d ",i);
             int k=i;
             while(nex[k])
              {
                  printf("%d ",nex[k]-n);
                  k=nex[k]-n;
              }
             printf("
    ");
         }
        printf("%d",ans);
        return 0;
    }
    View Code

    魔术球问题

    本题模型为最小路径覆盖问题。

    (i+j)为完全平方数即连边,图显然是DAG。

    题目变为添加尽量多的点使最小路径覆盖≤n(一条简单路径表示一根柱子)

    题目的关键在于答案不可知(即二分图点数不确定),所以从1开始枚举答案,每次两端加点,

    判断1..i-1是否有和i连边的(注意别连反了),然后在前一次的残余网络上建边再跑增广即可。

    枚举答案理论上应用二分,但二分无法利用上次的残余网络,反而慢。

    打印路径很烦,没AC……http://hzwer.com/1878.html

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    const int maxn=100000,inf=0x3f3f3f3f;
    int n,m,S,T,d[maxn],q[10010],first[maxn],tot=1,nex[maxn],nexnow[maxn];
    bool mark[maxn];
    struct edge{int from,v,flow;}e[maxn];
    void insert(int u,int v,int w)
    {tot++;e[tot].v=v;e[tot].flow=w;e[tot].from=first[u];first[u]=tot;
     tot++;e[tot].v=u;e[tot].flow=0;e[tot].from=first[v];first[v]=tot;}
    bool bfs()
    {
        memset(d,-1,4*(T+1));
        int head=0,tail=1;q[0]=S;d[S]=0;
        while(head<tail)
         {
             int x=q[head++];if(head>10000)head=0;
             for(int i=first[x];i;i=e[i].from)
              if(e[i].flow&&d[e[i].v]==-1)
               {
                   int y=e[i].v;
                   d[y]=d[x]+1;
                   q[tail++]=y;if(tail>10000)tail=0;
               }
         }
        return d[T]!=-1;
    }
    int dinic(int x,int a)
    {
        if(x==T||a==0)return a;
        int flow=0,f;
        for(int i=first[x];i;i=e[i].from)
         if(e[i].flow&&d[e[i].v]==d[x]+1&&(f=dinic(e[i].v,min(a,e[i].flow)))>0)
          {
                if(f>0)
                 {
                         nexnow[x]=e[i].v-1000;
                         //nex[x]=e[i].v-1000;
                 }
              e[i].flow-=f;
              e[i^1].flow+=f;
              a-=f;
              flow+=f;
              if(a==0)break;
          }
        return flow;
    }
    int main()
    {
        scanf("%d",&n);
        S=0,T=2000;int ans=0,i;
        for(i=1;;i++)
         {
             for(int j=1;j<i;j++)if((int)sqrt((i+j))*(int)sqrt((i+j))==(i+j))insert(j,i+1000,1);
             insert(S,i,1);insert(i+1000,T,1);
             for(int j=1;j<=i;j++)nex[j]=nexnow[j];
             while(bfs())ans+=dinic(S,inf);
             if(i-ans>n)break;
         }
        printf("%d
    ",--i);
    //    for(int j=1;j<=i;j++)
    //     {
    //         for(int k=first[j];k;k=e[k].from)
    //          if(!e[k].flow)
    //           {
    //               nex[j]=e[k].v-1000;
    //               break;
    //           }
    //         printf("nex[%d]=%d
    ",j,nex[j]);
    //     }
    //    printf("nex=%d
    ",nex[12]);
    //    for(int j=1;j<=i+1;j++)printf("nex[%d]=%d
    ",j,nex[j]);
        for(int j=1;j<=i;j++)
         if(!mark[j])
          {
              int k=j;printf("%d ",j);
              while(nex[k]!=-1000&&nex[k]!=0)
               {
                   printf("%d ",nex[k]);
                   mark[nex[k]]=1;
                   k=nex[k];
               }
              printf("
    ");
          }
        return 0;
    }
    View Code

     【圆桌问题

    S向xi连边容量为单位人数

    yi向T连边容量为餐桌人数

    xi每个节点向yi每个节点连边容量为1(限制每单位至多一个人在桌)

    太空飞行计划问题

    最大权闭合子图

    S向计划连边,仪器向T连边,依赖关系连边正无穷。

    ans=正权和-最小割

    餐巾问题

    这个问题的主要约束条件是每天的餐巾够用,而餐巾的来源可能是最新购买,也可能是前几天送洗的餐巾。每天用完的餐巾可以选择送到快洗部或慢洗部,或者留到下一天再处理。

    所以我们可以把每天要用的和用完的分离开处理,建模成二分图(每天用完的和需要的之间用ri联系)。

    左侧xi表示第i天用完的餐巾,其数量为ri,所以从S向Xi连接容量为ri的边作为限制。

    右侧Yi是第i天需要的餐巾,数量为ri,与T连接的边容量作为限制。

    每天用完的餐巾可以选择留到下一天(Xi->Xi+1),不需要花费;

    或送到快洗部(Xi->Yi+m),费用为f,送到慢洗部(Xi->Yi+n),费用为s。

    每天需要的餐巾除了刚刚洗好的餐巾,还可能是新购买的(S->Yi),费用为p。

    最小费用最大流。

  • 相关阅读:
    反射
    定义类
    集合list,set,Map
    API util
    进程与多线程
    泛型
    API string
    JDBC存储过程
    预处理
    JDBC
  • 原文地址:https://www.cnblogs.com/onioncyc/p/6720236.html
Copyright © 2011-2022 走看看