首页 > 代码库 > 【网络流24题】

【网络流24题】

网络流

网络流24题

【最小路径覆盖问题】

关于输出路径,因为即使有反向弧经过左侧点也一定会改变左侧点的去向,若没连向右侧就会被更新到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\n",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("\n");
     }
    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\n",--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\n",j,nex[j]);
//     }
//    printf("nex=%d\n",nex[12]);
//    for(int j=1;j<=i+1;j++)printf("nex[%d]=%d\n",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("\n");
      }
    return 0;
}
View Code

 

【网络流24题】