网络流 匈牙利算法 初见

今天学习了一下ISAP算法,简单的进行了网络流的实现。

 

cogs885 草地排水

题目大意:赤裸裸的网络流模板题。

#include<iostream>

#include<cstdio>

using namespace std;

int map[201][201]={0},dis[201]={0},gap[201]={0},pre[201]={0};

int sap(int stt,int enn)

{

    int i,y,ans=0,u;

    bool f=false;

    gap[0]=enn;u=stt;pre[stt]=stt;

    while(dis[stt]<enn)

    {

        f=false;

        for (i=1;i<=enn;++i)

        {

            if (map[u][i]>0&&dis[u]==dis[i]+1)

            {

                f=true;

                break;

            }

        }

        if (f)

        {

            pre[i]=u;u=i;

            if (u==enn)

            {

                y=2100000000;

                for (i=enn;i!=stt;i=pre[i])

                    y=min(y,map[pre[i]][i]);

                ans+=y;

                for (i=enn;i!=stt;i=pre[i])

                {

                    map[pre[i]][i]-=y;

                    map[i][pre[i]]+=y;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];y=enn;

            if (gap[dis[u]]==0) return ans;

            for (i=1;i<=enn;++i)

                if (map[u][i]>0&&dis[i]<y) y=dis[i];

            dis[u]=y+1;

            ++gap[dis[u]];

            u=pre[u];

        }

    }

    return ans;

}

int main()

{

    freopen("ditch.in","r",stdin);

    freopen("ditch.out","w",stdout);

    

    int n,m,i,j,si,ei,ci;

    scanf("%d%d",&m,&n);

    for (i=1;i<=m;++i)

    {

        scanf("%d%d%d",&si,&ei,&ci);

        map[si][ei]+=ci;

    }

    printf("%d\n",sap(1,n));

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs11运输问题1

题目大意:赤裸裸的网络流模板题。

思路:这一版是用的next数组,能处理较多边的情况,节省了空间和时间,不过写的时候总是忘记判断边权是否大于0,所以。。。

#include<iostream>

#include<cstdio>

using namespace std;

struct use{

    int st,en,va;

}edge[20001];

int point[101]={0},next[20001]={0},pre[101]={0},dis[101]={0},gap[101]={0};

int sap(int stt,int enn)

{

    int i,j,x,y,u,ans=0;

    bool f=false;

    gap[0]=enn;u=stt;

    while(dis[stt]<enn)

    {

        f=false;y=point[u];

        while (y!=0)

        {

            if (dis[u]==dis[edge[y].en]+1&&edge[y].va>0)

            {

                f=true;break;

            }

            y=next[y];

        }

        if (f)

        {

            pre[edge[y].en]=y;u=edge[y].en;y=2100000000;

            if (u==enn)

            {

                for (i=enn;i!=stt;i=edge[pre[i]].st)

                    y=min(y,edge[pre[i]].va);

                ans+=y;

                for (i=enn;i!=stt;i=edge[pre[i]].st)

                {

                      x=(pre[i]-1)^1+1;

                      edge[pre[i]].va-=y;

                      edge[x].va+=y;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            y=enn;

            for (i=point[u];i!=0;i=next[i])

              if (edge[i].va>0)

                y=min(y,dis[edge[i].en]);

            dis[u]=y+1;

            ++gap[dis[u]];

            if (u!=stt) u=edge[pre[u]].st;

        }

    }

    return ans;

}

int main()

{

    freopen("maxflowa.in","r",stdin);

    freopen("maxflowa.out","w",stdout);

    

    int n,i,j,tot=0,x;

    scanf("%d",&n);

    for (i=1;i<=n;++i)

      for (j=1;j<=n;++j)

      {

           scanf("%d",&x);

           if (x!=0)

           {

             ++tot;next[tot]=point[i];point[i]=tot;

             edge[tot].st=i;edge[tot].en=j;edge[tot].va=x;

             ++tot;next[tot]=point[j];point[j]=tot;

             edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;

         }

      }

    printf("%d\n",sap(1,n));

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

寒假测试 T1

题目大意:n*m的网格内放调料,一个格子上不能放不同颜色的调料(可以重复放一种),有三种放调料的方法:1)放一行上连续几个格子,代价为a;2)放一列上连续几个格子,代价为b;3)单独放一个格子,代价为c。求出放满调料的最小代价和。

思路:在测试的时候直接就跳过了这道题,组里有些搜索的全部超时。这道题的正解是网络流,因为对网络流的理解不够深刻,所以没能很快反应过来。

        应该对于读进来的nm矩阵进行预处理,将能横向放的所有格子都找出来(一定是同种颜色在一行上最长的连续子段),将他们与超级源点连边,容量为a;再找纵向的,与超级汇点连边,代价为b;最后将横条和纵条有交点的连边,代价为c。这样我们求出这个网络流的最小割,就是能满足题目要求的最小代价了。其实这样建图不难理解,我们其实就是将一个点放到三种情况里,只要是最小割就一定能填满所有格子,同时保证代价最小。

       在实现的时候,用next数组实现的sap,开小了数组(这里最多可能有6n^2的边,包括正反边,而不是3n^2),改了之后才很快的a了。

        现在发现网络流中的建模的过程十分复杂,情况很多,以后一定要多练习,多思考,熟练掌握。

#include<cstdio>

#include<iostream>

#include<cstring>

using namespace std;

struct use{

    int st,en,va;

}edge[10000];

int map[40][40]={0},tot,n,m,gap[3000]={0},dis[3000]={0},pre[3000]={0},mapp[40][40][2]={0},

    point[3000]={0},next[10000]={0};

void add(int stt,int enn,int vaa)

{

    ++tot;next[tot]=point[stt];point[stt]=tot;

    edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;

    ++tot;next[tot]=point[enn];point[enn]=tot;

    edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;

}

int sap(int st,int en)

{

    int i,j,u,v,minn,ans=0;

    bool f;

    memset(gap,0,sizeof(gap));

    memset(dis,0,sizeof(dis));

    memset(pre,0,sizeof(pre));

    gap[0]=en-st+1;u=st;

    while (dis[st]<en)

    {

        f=false;

        for (i=point[u];i!=0;i=next[i])

        {

            if (dis[edge[i].en]+1==dis[u]&&edge[i].va>0)

            {    

                f=true;break;

            }

        }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==en)

            {

                minn=2100000000;

                for (i=en;i!=st;i=edge[pre[i]].st)

                    minn=min(minn,edge[pre[i]].va);

                ans+=minn;

                for (i=en;i!=st;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=minn;

                    edge[pre[i]^1].va+=minn;

                }

                u=st;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            minn=en+1;

            for (i=point[u];i!=0;i=next[i])

              if (edge[i].va>0)

                minn=min(minn,dis[edge[i].en]);

            dis[u]=minn+1;

            ++gap[dis[u]];

            if (u!=st) u=edge[pre[u]].st;

        }

    }

    return ans;

}

int main()

{

    freopen("meatchickwing.in","r",stdin);

    freopen("meatchickwing.out","w",stdout);

    

    int t,i,j,a,b,c,x,y,totd,tot1;

    char ch;

    

    scanf("%d",&t);

    while(t)

    {

        memset(map,0,sizeof(map));

        memset(edge,0,sizeof(edge));

        memset(mapp,0,sizeof(mapp));

        memset(edge,0,sizeof(edge));

        memset(point,0,sizeof(point));

        memset(next,0,sizeof(next));

        totd=0;tot=1;

        scanf("%d%d%d%d%d",&n,&m,&a,&b,&c);

        for (i=1;i<=n;++i)

        {

          for (j=1;j<=m;++j)

          {

              cin>>ch;

            map[i][j]=ch-'a'+1;

          }

        }

        for (i=1;i<=n;++i)

          for (j=1;j<=m;++j)

          {

               if (map[i][j]!=map[i][j-1]) ++totd;

               mapp[i][j][0]=totd;

          }

        tot1=totd;

        for (j=1;j<=m;++j)

          for (i=1;i<=n;++i)

          {

            if (map[i][j]!=map[i-1][j]) ++totd;

            mapp[i][j][1]=totd;

          }

        for (i=1;i<=tot1;++i) add(0,i,a);

        for (i=tot1+1;i<=totd;++i) add(i,totd+1,b);

        for (i=1;i<=n;++i)

          for (j=1;j<=m;++j)

            add(mapp[i][j][0],mapp[i][j][1],c);

        printf("%d\n",sap(0,totd+1));

        --t;

    }

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs14搭配飞行员

题目大意:无源无汇网络流模板题。

思路:对于无源无汇问题,我们只需要加入超级源点和超级汇点,并向对应的点连边,容量为1(一名驾驶员只能安排一次)就可以了。

#include<iostream>

#include<cstdio>

#include<algorithm>

using namespace std;

struct use{

    int st,en,va;

}edge[10000];

int next[10000]={0},point[102]={0},dis[102]={0},pre[102]={0},gap[102]={0},tot;

void add(int i,int j,int v)

{

    ++tot;next[tot]=point[i];point[i]=tot;

    edge[tot].st=i;edge[tot].en=j;edge[tot].va=v;

    ++tot;next[tot]=point[j];point[j]=tot;

    edge[tot].st=j;edge[tot].en=i;edge[tot].va=v;

}

int sap(int stt,int enn)

{

    int i,j,u,v,minn,ans=0;

    bool f=false;

    gap[0]=enn-stt+1;u=stt;

    while(dis[stt]<enn)

    {

        f=false;

        for (i=point[u];i!=0;i=next[i])

            if (dis[u]==dis[edge[i].en]+1&&edge[i].va>0)

            {

                f=true;break;

            }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==enn)

            {

                minn=2100000000;

                for (i=enn;i!=stt;i=edge[pre[i]].st)

                  minn=min(minn,edge[pre[i]].va);

                ans+=minn;

                for (i=enn;i!=stt;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=minn;

                    edge[pre[i]^1].va+=minn;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            minn=enn+1;

            for (i=point[u];i!=0;i=next[i])

                if (edge[i].va)

                  minn=min(minn,dis[edge[i].en]);

            dis[u]=minn+1;

            ++gap[dis[u]];

        }

    }

    return ans;

}

int main()

{

    int n,m,i,j,a,b;

    

    freopen("flyer.in","r",stdin);

    freopen("flyer.out","w",stdout);

    

    tot=1;

    scanf("%d%d",&n,&m);

    while(scanf("%d%d",&a,&b)==2)

    {

        if (a>b) swap(a,b);

        add(a,b,1);

    }

    for (i=1;i<=m;++i) add(0,i,1);

    for (i=m+1;i<=n;++i) add(i,n+1,1);

    printf("%d\n",sap(0,n+1));



    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs12运输问题2

题目大意:有上下界的最大流

思路:建图的时候进行更改,从一条边的起点向终点连一条(x-y)(x为上限,y为下限)的边,从超级源点向终点连一条y的边,从起点向超级汇点连一条x的边,从原图中的汇点向源点连一条正无穷的边(为了使原图为无源无汇的)。做一遍sap,如果从超级源点流出的弧都满载,就是有可行解,这个时候汇点到源点的流量(也就是源点到汇点的残余流量->即edge.va的值)就是流过的下限的流量。删掉超级源汇点,以及汇点向源点连的那条正无穷的边,对源汇点做一遍最大流,与之前下限流量之和就是答案。

#include<iostream>

#include<cstdio>

#include<cstring>

using namespace std;

struct use{

    int st,en,va;

}edge[100001];

int point[202]={0},next[100001]={0},pre[202]={0},dis[202]={0},gap[202]={0},tot=0;

int sap(int stt,int enn)

{

    int i,j,x,y,u,ans=0;

    bool f=false;

    memset(gap,0,sizeof(gap));

    memset(dis,0,sizeof(dis));

    memset(pre,0,sizeof(pre));

    gap[0]=enn-stt+1;u=stt;

    while(dis[stt]<enn)

    {

        f=false;y=point[u];

        while (y!=0)

        {

            if (dis[u]==dis[edge[y].en]+1&&edge[y].va>0)

            {

                f=true;break;

            }

            y=next[y];

        }

        if (f)

        {

            pre[edge[y].en]=y;u=edge[y].en;

            if (u==enn)

            {

                y=2100000000;

                for (i=enn;i!=stt;i=edge[pre[i]].st)

                    y=min(y,edge[pre[i]].va);

                ans+=y;

                for (i=enn;i!=stt;i=edge[pre[i]].st)

                {

                      edge[pre[i]].va-=y;

                      edge[pre[i]^1].va+=y;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            y=enn+1;

            for (i=point[u];i!=0;i=next[i])

              if (edge[i].va>0)

                y=min(y,dis[edge[i].en]);

            dis[u]=y+1;

            ++gap[dis[u]];

            if (u!=stt) u=edge[pre[u]].st;

        }

    }

    return ans;

}

void add(int i,int j,int v)

{

    ++tot;next[tot]=point[i];point[i]=tot;

    edge[tot].st=i;edge[tot].en=j;edge[tot].va=v;

    ++tot;next[tot]=point[j];point[j]=tot;

    edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;

}

int main()

{

    freopen("maxflowb.in","r",stdin);

    freopen("maxflowb.out","w",stdout);

    

    int n,i,j,x,y,ans,t=0;

    bool f=false;

    scanf("%d",&n);

    tot=1;

    for (i=1;i<=n;++i)

      for (j=1;j<=n;++j)

      {

           scanf("%d%d",&y,&x);

           if (x!=0)

           {

                   if (y>0) 

              {

                 add(i,j,x-y);add(0,j,y);add(i,n+1,y);t+=y;

              }

              else add(i,j,x);

         }

      }

    add(n,1,1000000000);

    ans=sap(0,n+1);

    if (ans==t)

    {

      ans=edge[tot].va;

      edge[tot].va=edge[tot-1].va=0;

      for (i=point[0];i!=0;i=next[i]) edge[i].va=edge[i^1].va=0;

      for (i=point[n+1];i!=0;i=next[i]) edge[i].va=edge[i^1].va=0;

      printf("%d\n",sap(1,n)+ans);

    }

    else printf("0\n");

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs14运输问题4

题目大意:最小费用最大流

思路:用spfa做费用的最短路,更新可行流,然后增广就可以了,直到找不到可行流。

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 10000000

using namespace std;

struct use{

    int st,en,va,co;

}edge[20000];

int que[10000001]={0},pre[101]={0},a[101]={0},d[101]={0},flow=0,cost=0,point[101]={0},next[20000]={0},tot;

bool visit[101]={false};

void add(int i,int j,int va,int co)

{

    ++tot;next[tot]=point[i];point[i]=tot;

    edge[tot].st=i;edge[tot].en=j;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[j];point[j]=tot;

    edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;edge[tot].co=-co;

}

bool work(int s,int t)

{

    int head,tail,i,j,u,stan;

    memset(d,127,sizeof(d));

    memset(visit,false,sizeof(visit));

    memset(pre,0,sizeof(pre));

    d[s]=0;visit[s]=true;stan=a[s]=d[0];

    head=0;tail=1;que[tail]=s;

    while(head!=tail)

    {

        head=head%len+1;

        u=que[head];

        visit[u]=false;

        for (i=point[u];i!=0;i=next[i])

        {

            if (edge[i].va>0&&d[edge[i].en]>d[u]+edge[i].co)

            {

                d[edge[i].en]=d[u]+edge[i].co;

                pre[edge[i].en]=i;

                a[edge[i].en]=min(a[u],edge[i].va);

                if (!visit[edge[i].en])

                {

                    visit[edge[i].en]=true;

                    tail=tail%len+1;

                    que[tail]=edge[i].en;

                }

            }

        }

    }

    if (d[t]==stan) return false;

    flow+=a[t];

    cost+=d[t]*a[t];

    for (u=t;u!=s;u=edge[pre[u]].st)

    {

        edge[pre[u]].va-=a[t];

        edge[pre[u]^1].va+=a[t];

    }

    return true;

}

int main()

{

    freopen("maxflowd.in","r",stdin);

    freopen("maxflowd.out","w",stdout);

    

    int n,s,t,i,j,va,co;

    scanf("%d%d%d",&n,&s,&t);

    tot=1;

    for (i=1;i<=n;++i)

      for (j=1;j<=n;++j)

      {

          scanf("%d%d",&va,&co);

          if (va)

            add(i,j,va,co);

      }

    while(work(s,t));

    printf("%d\n",cost);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

codevs2436修车

题目大意:有m个工人,n个顾客,已知每个工人修相应车的时间,求最少平均等待时间。

思路:建三排点,从超级源点向每个顾客引一条边,容量为1,费用为0;从每个顾客向ci,j(i表示顾客,j表示工人)引一条边,容量为1,费用为0;从每个ci,j向kkj,k(j表示工人,k表示该工人倒数第k顺序修的车)引一条边,容量为1,费用为k*time[i][j](包括本身共k个人等time[i][j]的时间);从每个kkj,k向超级汇点引一条边,容量为1,费用为0。从超级源点向超级汇点做费用流,答案就是了。(当然也可以删掉第二排点,然后也可以做出解。)

         建图的思路要明确,然后就很简单了。

#include<iostream>

#include<cstdio>

#include<cstring> 

#define len 1000000

using namespace std;

struct use{

    int st,en,va,co;

}edge[100000];

int que[1000001]={0},d[2001]={0},pre[2001]={0},a[2001]={0},point[2001]={0},next[100000]={0},flow=0,cost=0,

    tot,ti[61][10]={0},c[61][10]={0},kk[10][61]={0};

bool visit[2001]={false};

void add(int i,int j,int va,int co)

{

    ++tot;next[tot]=point[i];point[i]=tot;

    edge[tot].st=i;edge[tot].en=j;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[j];point[j]=tot;

    edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;edge[tot].co=-co;

}

bool work(int s,int t)

{

    int head,tail,u,i,j,stan;

    memset(visit,false,sizeof(visit));

    memset(d,127,sizeof(d));stan=d[0];

    memset(a,0,sizeof(a));

    memset(pre,0,sizeof(pre));

    head=0;tail=1;que[tail]=s;visit[s]=true;d[s]=0;a[s]=stan;

    while(head!=tail)

    {

        head=head%len+1;

        u=que[head];visit[u]=false;

        for (i=point[u];i!=0;i=next[i])

        {

            if (edge[i].va>0&&d[edge[i].en]>d[u]+edge[i].co)

            {

                d[edge[i].en]=d[u]+edge[i].co;

                a[edge[i].en]=min(a[u],edge[i].va);

                pre[edge[i].en]=i;

                if (!visit[edge[i].en])

                {

                    tail=tail%len+1;que[tail]=edge[i].en;visit[edge[i].en]=true;

                }

            }

        }

    }

    if (d[t]==stan) return false;

    flow+=a[t];

    cost+=d[t]*a[t];

    for (u=t;u!=s;u=edge[pre[u]].st)

    {

        edge[pre[u]].va-=a[t];

        edge[pre[u]^1].va+=a[t];

    }

    return true;

}

int main()

{

    int n,m,i,j,k,dian;

    double ans;

    scanf("%d%d",&m,&n);

    tot=1;

    for (i=1;i<=n;++i)

      for (j=1;j<=m;++j)

          scanf("%d",&ti[i][j]);

    for (i=1;i<=n;++i) add(0,i,1,0);

    dian=n;

    for (i=1;i<=n;++i)

      for (j=1;j<=m;++j)

      {

           ++dian;

           c[i][j]=dian;

           add(i,dian,1,0);

      }

    for (j=1;j<=m;++j)

      for (k=1;k<=n;++k)

      {

          ++dian;kk[j][k]=dian;

      }

    for (i=1;i<=n;++i)

      for (j=1;j<=m;++j)

        for (k=1;k<=n;++k)

        {

          add(c[i][j],kk[j][k],1,k*ti[i][j]);

        }

    ++dian;

    for (j=1;j<=m;++j)

      for (k=1;k<=n;++k)

        add(kk[j][k],dian,1,0);

    while(work(0,dian));

    ans=cost*1.0/n;

    printf("%0.2f\n",ans);

}
View Code

 

cogs1366美食节(noi2012)

题目大意:同修车,只是改大了范围,每种菜可能有多道。

思路:同样建三排点,只是源点到第一排点的容量为p[i],第一排点到第二排点容量为p[i],其余不变,这样能过6个点,4个tle。

        于是有一个优化,对于第三排点,如果倒数第k道菜没选好,那么第k+1道菜也没有安排,所以我们就在每个厨师的第k道菜安排好后,在加第k+1道菜的点和相应的边(每次增广之后。一定要注意加那些边,这里出了好久的问题。。。)

#include<iostream>

#include<cstdio>

#include<cstring> 

#define len 100000

using namespace std;

struct use{

    int st,en,va,co;

}edge[1000000];

int que[100001]={0},d[100000]={0},pre[100000]={0},a[100000]={0},point[100000]={0},next[1000000]={0},flow=0,cost=0,

    tot,ti[41][101]={0},c[41][101]={0},p[41],lev[101]={0},dian,n,m,ji[100000]={0},sum=0,tt;

bool visit[100000]={false};

void add(int i,int j,int va,int co)

{

    ++tot;next[tot]=point[i];point[i]=tot;

    edge[tot].st=i;edge[tot].en=j;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[j];point[j]=tot;

    edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;edge[tot].co=-co;

}

bool work(int s,int t)

{

    int head,tail,u,i,j,stan;

    memset(visit,false,sizeof(visit));

    memset(d,127,sizeof(d));stan=d[0];

    head=0;tail=1;que[tail]=s;visit[s]=true;d[s]=0;a[s]=stan;

    while(head!=tail)

    {

        head=head%len+1;

        u=que[head];visit[u]=false;

        for (i=point[u];i!=0;i=next[i])

        {

            if (edge[i].va>0&&d[edge[i].en]>d[u]+edge[i].co)

            {

                d[edge[i].en]=d[u]+edge[i].co;

                a[edge[i].en]=min(a[u],edge[i].va);

                pre[edge[i].en]=i;

                if (!visit[edge[i].en])

                {

                    tail=tail%len+1;que[tail]=edge[i].en;visit[edge[i].en]=true;

                }

            }

        }

    }

    if (d[t]==stan) return false;

    flow+=a[t];

    cost+=d[t]*a[t];

    for (u=t;u!=s;u=edge[pre[u]].st)

    {

        edge[pre[u]].va-=a[t];

        edge[pre[u]^1].va+=a[t];

    }

    int cc=ji[edge[pre[t]].st];

    ++lev[cc];++dian;

    ji[dian]=cc;

    for (i=1;i<=n;++i)

        add(c[i][cc],dian,1,lev[cc]*ti[i][cc]);

    add(dian,tt,1,0);

    return true;

}

int main()

{

    freopen("noi12_delicacy.in","r",stdin);

    freopen("noi12_delicacy.out","w",stdout);

    

    int i,j,k;

    scanf("%d%d",&n,&m);

    tot=1;

    for (i=1;i<=n;++i) 

    {

        scanf("%d",&p[i]);

        sum+=p[i];

    }

    for (i=1;i<=n;++i)

      for (j=1;j<=m;++j)

          scanf("%d",&ti[i][j]);

    for (i=1;i<=n;++i) add(0,i,p[i],0);

    dian=n;

    for (i=1;i<=n;++i)

      for (j=1;j<=m;++j)

      {

           ++dian;

           c[i][j]=dian;

           add(i,dian,p[i],0);

      }

    for (i=1;i<=n;++i)

      for (j=1;j<=m;++j)

      {

          ji[dian+j]=j;lev[j]=1;

        add(c[i][j],dian+j,1,ti[i][j]);

      }

    dian+=m;

    ++dian;

    for (j=1;j<=m;++j)

        add(dian-1-m+j,dian,1,0);

    tt=dian;

    while(work(0,tt));

    printf("%d\n",cost);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs28||bzoj1497 最大获利

题目大意:最大权闭合子图模板题

思路:建两排点,由每一个用户群向中转站连有向边,用户群的点权为ci,中转站的点权为-pi。然后就是最大权闭合子图的部分了,从S向正权的点连容量为权的边,从负权向T连容量为|权|的边,原图中的边容量都为正无穷,然后跑最大流,所有正点权和-最大流就是答案了(这里其实是最小割的思路)。

(突然发现自己从来没有用过当前弧cur优化,加了当前弧优化之后,竟然快了。(无)。(数)。(倍)!!!)

#include<iostream>

#include<cstdio>

#define stan 2100000000LL

using namespace std;

struct use{

    int st,en,va;

}edge[500000];

int pp[5001]={0},mi[50001]={0},tot,point[56000]={0},next[500000]={0},dis[56000]={0},gap[56000]={0},pre[56000]={0},

    cur[56000]={0},n,m;

void add(int stt,int enn,int vaa)

{

    ++tot;

    next[tot]=point[stt];point[stt]=tot;edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;

    ++tot;

    next[tot]=point[enn];point[enn]=tot;edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;

}

int sap(int stt,int enn)

{

    int u,i,j,ans=0;

    bool f=false;

    for (i=1;i<=n+m+1;++i) cur[i]=point[i];

    gap[0]=enn-stt+1;u=stt;

    while(dis[enn]<enn-stt+1)

    {

        f=false;

        for (i=cur[u];i!=0;i=next[i])

        {

            if (dis[edge[i].en]+1==dis[u]&&edge[i].va>0)

            {

                cur[u]=i;

                f=true;break;

            }

        }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==enn)

            {

                j=stan;

                for (i=u;i!=stt;i=edge[pre[i]].st)

                    j=min(j,edge[pre[i]].va);

                ans+=j;

                for (i=u;i!=stt;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=j;

                    edge[pre[i]^1].va+=j;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            j=stan;

            for (i=point[u];i!=0;i=next[i])

                if (edge[i].va)

                  j=min(j,dis[edge[i].en]);

            dis[u]=j+1;

            cur[u]=point[u];

            ++gap[dis[u]];

            if (u!=stt) u=edge[pre[u]].st;

        }

    }

    return ans;

}

int main()

{

    freopen("profit.in","r",stdin);

    freopen("profit.out","w",stdout);

    

    int i,j,ai,bi,ci,ans=0;

    tot=1;

    scanf("%d%d",&n,&m);

    for (i=1;i<=n;++i) 

    {

        scanf("%d",&pp[i]);

        add(i,n+m+1,pp[i]);

    }

    for (i=1;i<=m;++i)

    {

        mi[i]=n+i;

        scanf("%d%d%d",&ai,&bi,&ci);

        add(0,mi[i],ci);add(mi[i],ai,stan);add(mi[i],bi,stan);

        ans+=ci;

    }

    i=sap(0,n+m+1);

    ans-=i;

    printf("%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs420 SDOI2009晨跑

题目大意:无交叉点的费用流。

思路:拆点,然后就可以保证无交叉点,然后就费用流。。。

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 10000000LL

using namespace std;

struct use{

    int st,en,va,co;

}edge[100000];

int next[100000]={0},point[401]={0},pre[401]={0},a[401]={0},d[401]={0},n,m,tot=0,day=0,cost=0,flow=0,

    que[10000001]={0},qian[201]={0},hou[201]={0};

bool visit[401]={false};

void add(int stt,int enn,int vaa,int coo)

{

    ++tot;next[tot]=point[stt];point[stt]=tot;

    edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;edge[tot].co=coo;

    ++tot;next[tot]=point[enn];point[enn]=tot;

    edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;edge[tot].co=-coo;

}

bool work(int stt,int enn)

{

    int head,tail,i,j,x;

    memset(visit,false,sizeof(visit));

    memset(d,127,sizeof(d));

    memset(a,127,sizeof(a));

    memset(pre,0,sizeof(pre));

    head=0;tail=1;que[tail]=stt;visit[stt]=true;d[stt]=0;

    while(head!=tail)

    {

        head=head%len+1;

        x=que[head];visit[x]=false;

        for (i=point[x];i!=0;i=next[i])

        {

            if (edge[i].va>0&&d[edge[i].en]>d[x]+edge[i].co)

            {

                d[edge[i].en]=d[x]+edge[i].co;

                a[edge[i].en]=min(a[x],edge[i].va);

                pre[edge[i].en]=i;

                if (!visit[edge[i].en])

                {

                    visit[edge[i].en]=true;

                    tail=tail%len+1;

                    que[tail]=edge[i].en;

                }

            }

        }

    }

    if (d[enn]==d[0]) return false;

    flow+=a[enn];

    cost+=a[enn]*d[enn];

    for (i=enn;i!=stt;i=edge[pre[i]].st)

    {

        edge[pre[i]].va-=a[enn];

        edge[pre[i]^1].va+=a[enn];

    }

    return true;

}

int main()

{

    freopen("run.in","r",stdin);

    freopen("run.out","w",stdout);

    

    int i,j,a,b,c;

    scanf("%d%d",&n,&m);

    qian[1]=hou[1]=1;qian[n]=hou[n]=2*n-2;tot=1;

    for (i=2;i<n;++i)

    {

        qian[i]=i*2-2;hou[i]=i*2-1;

    }

    for (i=2;i<n;++i)

        add(qian[i],hou[i],1,0);

    for (i=1;i<=m;++i)

    {

        scanf("%d%d%d",&a,&b,&c);

        add(hou[a],qian[b],1,c);

    }

    while(work(qian[1],hou[n])) ++day;

    printf("%d %d\n",day,cost);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs664 SDOI2010星际竞速

题目大意:貌似是个很像路径覆盖的东西,只是有一些费用,然后求路径覆盖方案中费用最小的。

思路:这里的无向边一定是有向边,毕竟只能从小到大。一开始想不拆点,然后做,但是发现一条路径上的co与流量有关,而流量却不一定为1,所以可能一条路的co加了多遍。就这样卡住了,就这样陷入了死循环。。。后来发现了拆点,因为每一个点都经过一次,所以我们用这个点'表示它经过了,向汇点连边;而它经过的方式有两种,一种是通过之前的点过来,一种是通过跳跃过来,对于跳跃的就是从超级源点跳过来,所以就连出两种边;对于这个点能连出去的话,就用点连向所有的点',又出现一种边。所有边的容量都为1,费用相应的。。。然后费用流就可以了。(一定要掌握拆点这些神奇的技巧)

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 10000000LL

using namespace std;

struct use{

    int st,en,va,co;

}edge[100000];

int que[10000001]={0},a[2000]={0},d[2000]={0},pre[2000]={0},next[100000]={0},point[2000]={0},tot=0,n,m,cost;

bool visit[2000]={false};

void add(int stt,int enn,int vaa,int coo)

{

    ++tot;next[tot]=point[stt];point[stt]=tot;

    edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;edge[tot].co=coo;

    ++tot;next[tot]=point[enn];point[enn]=tot;

    edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;edge[tot].co=-coo;

}

bool work(int stt,int enn)

{

    int head,tail,i,j,x;

    memset(pre,0,sizeof(pre));

    memset(a,127,sizeof(a));

    memset(d,127,sizeof(d));

    memset(visit,false,sizeof(visit));

    head=0;tail=1;que[1]=stt;visit[stt]=true;d[stt]=0;

    while(head!=tail)

    {

        head=head%len+1;

        x=que[head];visit[x]=false;

        for (i=point[x];i!=0;i=next[i])

        {

            if (edge[i].va>0&&d[edge[i].en]>d[x]+edge[i].co)

            {

                d[edge[i].en]=d[x]+edge[i].co;

                a[edge[i].en]=min(edge[i].va,a[x]);

                pre[edge[i].en]=i;

                if (!visit[edge[i].en])

                {

                    visit[edge[i].en]=true;

                    tail=tail%len+1;

                    que[tail]=edge[i].en;

                }

            }

        }

    }

    if (d[enn]==d[1999]) return false;

    cost+=d[enn]*a[enn];

    for (i=enn;i!=stt;i=edge[pre[i]].st)

    {

        edge[pre[i]].va-=a[enn];

        edge[pre[i]^1].va+=a[enn];

    }

    return true;

}

int main()

{

    freopen("starrace.in","r",stdin);

    freopen("starrace.out","w",stdout);

    

    int n,m,i,j,ai,s,t,wi;

    tot=1;

    scanf("%d%d",&n,&m);

    for (i=1;i<=n;++i) 

    {

       scanf("%d",&ai);

       add(0,i,1,0);

       add(0,i+n,1,ai);

       add(i+n,n*2+1,1,0);

    }

    for (i=1;i<=m;++i)

    {

        scanf("%d%d%d",&s,&t,&wi);

        if (s>t) swap(s,t);

        add(s,t+n,1,wi);

    }

    while(work(0,2*n+1));

    printf("%d\n",cost);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs728最小路径覆盖

题目大意:最小路径覆盖模板题,要输出路径。

 思路:题目中竟然给出了建图。。。把每个点都拆成两个,从超级源点向前一个点连边①,从后一个点向汇点连边②,从起点的前一个点向终点的后一个点连边③,容量都是1,然后跑最大流,n-最大流就是答案了(其实最大流就是路径中的那些边的个数)。对于路径来说,每一条流量为0的边③都是路径上的,我们穷举路径的起点,一个路径的起点一定没有没被访问过得别的边③流量为0且终点为起点的后一个点,所以我们找点的时候judge一下,然后从起点往后递归这输出一下就可以了,因为一个点只能有一个终点(只访问一遍),所以不用担心多条路径。

貌似可以用匈牙利算法直接做,小小的学习了一下匈牙利算法(看到一篇博文里说:“匈牙利算法就是‘有机会就上,没机会创造机会上’。”),发现会简单一些。

#include<iostream>

#include<cstdio>

using namespace std;

struct use{

    int st,en,va,co;

}edge[20000];

int point[500]={0},next[20000]={0},pre[500]={0},dis[500]={0},gap[500]={0},tot,cur[500]={0},n;

bool visit[500]={false};

void add(int stt,int enn,int vaa)

{

    ++tot;next[tot]=point[stt];point[stt]=tot;

    edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;

    ++tot;next[tot]=point[enn];point[enn]=tot;

    edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;

}

int sap(int stt,int enn)

{

    int i,j,u,ans=0;

    bool f=false;

    gap[0]=enn-stt+1;u=stt;

    for (i=stt;i<=enn;++i) cur[i]=point[i];

    while(dis[stt]<enn-stt+1)

    {

        f=false;

        for (i=cur[u];i!=0;i=next[i])

        {

            if (edge[i].va>0&&dis[edge[i].en]+1==dis[u])

            {

                cur[u]=i;f=true;break;

            }

        }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==enn)

            {

                j=2100000000;

                for (i=u;i!=stt;i=edge[pre[i]].st)

                    j=min(j,edge[pre[i]].va);

                ans+=j;

                for (i=u;i!=stt;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=j;

                    edge[pre[i]^1].va+=j;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            j=enn-stt+1;

            for (i=point[u];i!=0;i=next[i])

                if (edge[i].va>0)

                  if (dis[edge[i].en]<j) 

                    j=dis[edge[i].en];

            dis[u]=j+1;cur[u]=point[u];

            ++gap[dis[u]];

            if (u!=stt) u=edge[pre[u]].st;

        }

    }

    return ans;

}

bool judge(int j)

{

    int i;

    for (i=point[j+n];i!=0;i=next[i])

    {

        if (edge[i].st>0&&edge[i].st<=n)

            if (!visit[edge[i].st]&&edge[i].va==0) return false;

    }

    return true;

}

void print(int j)

{

    int i;

    visit[j]=true;printf("%d ",j);

    for (i=point[j];i!=0;i=next[i])

    {

        if (edge[i].en>n&&edge[i].en<=2*n)

          if (edge[i].va==0) print(edge[i].en-n);

    }

}

int main()

{

    freopen("path3.in","r",stdin);

    freopen("path3.out","w",stdout);

    

    int m,i,j,s,t,ans;

    tot=1;

    scanf("%d%d",&n,&m);

    for (i=1;i<=n;++i)

    {

        add(0,i,1);add(i+n,2*n+1,1);

    }

    for (i=1;i<=m;++i)

    {

        scanf("%d%d",&s,&t);

        add(s,t+n,1);

    }

    ans=n-sap(0,2*n+1);

    for (i=1;i<=ans;++i)

    {

        for (j=1;j<=n;++j)

          if (!visit[j]&&judge(j))

            break;

        print(j);printf("\n");

    }

    printf("%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
View Code
#include<iostream>

#include<cstdio>

#include<cstring>

using namespace std;

int map[300][300]={0},match[300]={0},go[300]={0};

bool visit[300]={false};

bool find(int i)

{

    int j;

    for (j=1;j<=map[i][0];++j)

    {

        if (!visit[map[i][j]])

        {

            visit[map[i][j]]=true;

            if (!match[map[i][j]]||find(match[map[i][j]]))

            {

                match[map[i][j]]=i;go[i]=map[i][j];

                return true;

            }

        }

    }

    return false;

}

int main()

{

    freopen("path3.in","r",stdin);

    freopen("path3.out","w",stdout);

    

    int n,m,i,j,s,t,ans=0;

    scanf("%d%d",&n,&m);

    for (i=1;i<=m;++i)

    {

        scanf("%d%d",&s,&t);

        ++map[s][0];map[s][map[s][0]]=t;

    }

    for (i=1;i<=n;++i)

    {

        memset(visit,false,sizeof(visit));

        if(!find(i)) ++ans;

    }

    for (i=1;i<=n;++i)

    {

        if (!match[i])

        {

            j=i;

            do{

                printf("%d ",j);

                j=go[j];

            }while(j);

            printf("\n");

        }

    }

    printf("%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
匈牙利算法

 

cogs738数字梯形

题目大意:找m条路线,分别满足三种条件,并输出最大和(有点像数字三角形)。

思路:根据三种条件建三次图,注意的是这m条路径的起点不能相同,正好m条路径的起点分别为第一行的m个点,然后跑最大费用流就可以了。

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 1000000

using namespace std;

struct use{

    int st,en,va,co;

}edge[10000]={0};

int tot,next[10000]={0},point[5000]={0},dian[21][40][2]={0},map[21][40]={0},que[1000001],dis[5000]={0},a[5000]={0},pre[5000]={0},ans;

bool visit[5000]={false};

void add(int st,int en,int va,int co)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co;

}

bool sap(int st,int en)

{

    int head,tail,i,j,x,y;

    memset(dis,128,sizeof(dis));

    memset(a,127,sizeof(a));

    memset(visit,false,sizeof(visit));

    dis[st]=0;head=0;tail=1;que[1]=st;visit[st]=true;

    while(head!=tail)

    {

        head=head%len+1;

        x=que[head];visit[x]=false;

        for (y=point[x];y!=0;y=next[y])

        {

            if (edge[y].va>0&&dis[edge[y].en]<edge[y].co+dis[x])

            {

                dis[edge[y].en]=edge[y].co+dis[x];

                a[edge[y].en]=min(a[x],edge[y].va);

                pre[edge[y].en]=y;

                if (!visit[edge[y].en])

                {

                    visit[edge[y].en]=true;tail=tail%len+1;que[tail]=edge[y].en;

                }

            }

        }

    }

    if (dis[en]==dis[0]) return false;

    ans+=dis[en]*a[en];

    for (x=en;x!=st;x=edge[pre[x]].st)

    {

        edge[pre[x]].va-=a[en];

        edge[pre[x]^1].va+=a[en];

    }

    return true;

}

int main()

{

    freopen("digit.in","r",stdin);

    freopen("digit.out","w",stdout);

    

    int n,m,i,j,totd=0,enn;

    scanf("%d%d",&m,&n);

    totd=2;

    for (i=1;i<=n;++i)

        for (j=1;j<m+i;++j)

        {

            scanf("%d",&map[i][j]);

            dian[i][j][0]=++totd;dian[i][j][1]=++totd;

        }

    enn=++totd;

    

    tot=1;add(1,2,m,0);

    for (i=1;i<=m;++i) add(2,dian[1][i][0],1,0);

    for (i=1;i<=n;++i)

      for (j=1;j<m+i;++j)

      {

           add(dian[i][j][0],dian[i][j][1],1,map[i][j]);

           if (i<n)

           {

               add(dian[i][j][1],dian[i+1][j][0],1,0);

               add(dian[i][j][1],dian[i+1][j+1][0],1,0);

           }

           else

               add(dian[i][j][1],enn,1,0);

      }

    ans=0;

    while(sap(1,enn));

    printf("%d\n",ans);

    

    memset(next,0,sizeof(next));

    memset(point,0,sizeof(point));

    memset(edge,0,sizeof(edge));

    tot=1;add(1,2,m,0);

    for (i=1;i<=m;++i) add(2,dian[1][i][0],1,0);

    for (i=1;i<=n;++i)

      for (j=1;j<m+i;++j)

      {

          add(dian[i][j][0],dian[i][j][1],m,map[i][j]);

          if (i<n)

          {

              add(dian[i][j][1],dian[i+1][j][0],1,0);

              add(dian[i][j][1],dian[i+1][j+1][0],1,0);

          }

          else

            add(dian[i][j][1],enn,m,0);

      }

    ans=0;

    while(sap(1,enn));

    printf("%d\n",ans);

    

    memset(next,0,sizeof(next));

    memset(point,0,sizeof(point));

    memset(edge,0,sizeof(edge));

    tot=1;add(1,2,m,0);

    for (i=1;i<=m;++i) add(2,dian[1][i][0],1,0);

    for (i=1;i<=n;++i)

      for (j=1;j<m+i;++j)

      {

          add(dian[i][j][0],dian[i][j][1],m,map[i][j]);

          if (i<n)

          {

              add(dian[i][j][1],dian[i+1][j][0],m,0);

              add(dian[i][j][1],dian[i+1][j+1][0],m,0);

          }

          else

            add(dian[i][j][1],enn,m,0);

      }

    ans=0;

    while(sap(1,enn));

    printf("%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs746 骑士共存

题目大意:在n*n的棋盘上放马,要求互不攻击,求最多能放多少个。

思路:为了构建二分图,我们把棋盘黑白染色,黑色格子只能向白色连边(马跳跃的性质),然后找最大匹配,用总点数(无障碍的)-最大匹配数就是答案。

注意:二分图,把棋盘分成两部分的思想。(这就是二分图最大独立子集问题,证明比较简单,我们可以看做删掉最少的点,使剩下的点之间没有连边,即用最小覆盖的答案,而最小覆盖又等于最大匹配,所以就可以表示成总点数-最大匹配数了。)

#include<iostream>

#include<cstdio>

#include<cstring>

using namespace std;

int db[201][201],match[40001]={0},next[700000]={0},point[40001]={0},en[700000]={0},tot=0,

    dx[8]={-1,-2,-2,-1,1,2,2,1},dy[8]={-2,-1,1,2,-2,-1,1,2};

bool visit[40001]={false};

void add(int st,int enn)

{

    ++tot;next[tot]=point[st];point[st]=tot;en[tot]=enn;

    ++tot;next[tot]=point[enn];point[enn]=tot;en[tot]=st;

}

bool find(int x)

{

    int i;

    for (i=point[x];i!=0;i=next[i])

    {

        if (!visit[en[i]])

        {

            visit[en[i]]=true;

            if (!match[en[i]]||find(match[en[i]]))

            {

                match[en[i]]=x;return true;

            }

        }

    }

    return false;

}

int main()

{

    freopen("knight.in","r",stdin);

    freopen("knight.out","w",stdout);

    

    int i,j,n,m,totd=0,x,y,t,ans=0;

    scanf("%d%d",&n,&m);

    for (i=1;i<=n;++i)

      for (j=1;j<=n;++j)

          db[i][j]=++totd;

    for (i=1;i<=m;++i)

    {

        scanf("%d%d",&x,&y);

        db[x][y]=0;

    }

    for (i=1;i<=n;++i)

      for (j=1;j<=n;++j)

      {

          if (db[i][j]&&((i+j)%2==0))

          {

              for (t=0;t<8;++t)

              {

                  x=i+dx[t];y=j+dy[t];

                  if (x<1||x>n) continue;

                  if (y<1||y>n) continue;

                  if (!db[x][y]) continue;

                  add(db[i][j],db[x][y]);

              }

          }

      }

    for (i=1;i<=n;++i)

      for (j=1;j<=n;++j)

      {

            if (!db[i][j]||(i+j)%2==1) continue;

          memset(visit,false,sizeof(visit));

            if (find(db[i][j])) ++ans;

      }

    ans=totd-ans-m;

    printf("%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs461餐巾||bzoj1221软件开发

题目大意:我们知道n天里每天需要的毛巾数,我们可以新买毛巾(花费f),也可以把一天用过的毛巾消毒再用(两种方法:要a天的花费fa;要b天的花费fb),然后求满足n天要求的最小花费。

思路:纠结了好久的题目。其实很像之前做过的星际竞速。对于一天的毛巾我们有新毛巾和旧毛巾两排点,为满足新毛巾的要求,我们从源点向这排点连流量正无穷,花费f的边(代表新买的);从旧毛巾里a+1或b+1天前的点连边为流量正无穷,花费fa或fb的边(代表洗的毛巾);从新毛巾向汇点连流量ni[i],花费0的边,满足每天有这么多天毛巾用。为了同时更新旧毛巾,从源点向旧毛巾这排点连流量ni[i]花费0的边,表示这一天用过的毛巾;从上一点的旧毛巾连下来,表示原来的旧毛巾可以留到今天(说成隔几天在洗,遭到了大神的鄙视。。。其实有点像个优化,如果不连这条边的话,就要从这个点向新毛巾里的i+a+1...i+a+j(i+a+j==n)、i+b+1...i+b+j(i+b+j==n)连边了,复杂度将大大提高。)然后跑费用流就可以了。

注意:网络流的构图十分重要,对于很多相似的情况要能熟练应用和理解。

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 1000000

#define inf 2100000000LL

using namespace std;

struct use{

    int st,en,va,co;

}edge[20001]={0};

int point[2010]={0},next[20001]={0},dis[2010],a[2010],pre[2010]={0},que[1000001]={0},ni[2010],ans=0,tot;

bool visit[2010]={false};

void add(int st,int en,int va,int co)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co;

}

bool work(int st,int en)

{

    int head,tail,x,y;

    memset(visit,false,sizeof(visit));

    memset(dis,127,sizeof(dis));

    memset(pre,0,sizeof(pre));

    head=0;tail=1;que[tail]=st;visit[st]=true;a[st]=inf;dis[st]=0;

    while(head!=tail)

    {

        head=head%len+1;

        x=que[head];visit[x]=false;

        for (y=point[x];y;y=next[y])

        {

            if (edge[y].va&&dis[x]+edge[y].co<dis[edge[y].en])

            {

                dis[edge[y].en]=edge[y].co+dis[x];

                pre[edge[y].en]=y;

                a[edge[y].en]=min(a[x],edge[y].va);

                if (!visit[edge[y].en])

                {

                    visit[edge[y].en]=true;

                    tail=tail%len+1;que[tail]=edge[y].en;

                }

            }

        }

    }

    if (dis[en]==dis[2009]) return false;

    ans+=a[en]*dis[en];

    for (x=en;x!=st;x=edge[pre[x]].st)

    {

        edge[pre[x]].va-=a[en];

        edge[pre[x]^1].va+=a[en];

    }

    return true;

}

int main()

{

    int n,a,b,f,fa,fb,en,i;

    scanf("%d%d%d%d%d%d",&n,&a,&b,&f,&fa,&fb);

    en=2*n+1;tot=1;

    for (i=1;i<=n;++i) scanf("%d",&ni[i]);

    for (i=1;i<=n;++i)

    {

        add(0,i*2-1,ni[i],f);

        add(0,i*2,ni[i],0);

        if (i<n) add(i*2,(i+1)*2,inf,0);

        if (i+a+1<=n) add(i*2,(i+a+1)*2-1,inf,fa);

        if (i+b+1<=n) add(i*2,(i+b+1)*2-1,inf,fb);

        add(i*2-1,en,ni[i],0);

    }

    while(work(0,en));

    printf("%d\n",ans);

}
View Code

 

cogs396魔术球问题

题目大意:按顺序放入1、2、3...的球在n个柱子上,使相邻的两个球和为完全平方数,问最多能放多少个。

思路:当做贪心来写的。后来明白了怎么跟网络流扯上关系,我们穷举答案,然后将1...ans内能构成相邻的数从小到大连一条边,然后就是最小路径覆盖了,如果路径数<=n就是合理的,找到第一个超过n的ans,ans-1就是答案了,可以用网络流也可以用匈牙利做。不过原题好像要输出方案,不知道是不是要special judge。

(好像看到一个黑心的公式:(n^2+2*n-1)div 2)(自己写的贪心,所以就不贴code了。。。)

 

cogs729圆桌聚餐

题目大意:m个单位,n张桌子,要求同一单位的人不在同一桌上,如果能安排下就输出1和方案,如果不能就输出0。

思路:最大流的做法。建两排点,从源点向单位连边为单位的人,从桌子向汇点连边为桌子的容量,每个单位向每个桌子连边为1,跑最大流,如果等于所有单位的人数,就是有解,然后找一下残余网络中一二排点之间流量为0的边就是一个安排,然后相应的输出就可以了。

#include<iostream>

#include<cstdio>

#include<cstring>

#define inf 2100000000

using namespace std;

struct use{

    int st,en,va;

}edge[100000]={0};

int ri[151]={0},ci[271]={0},ni[271]={0},next[100000]={0},point[450]={0},cur[450]={0},n,m,

    gap[450]={0},pre[450]={0},dis[450]={0},tot;

void add(int st,int en,int va)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;

}

int sap(int st,int en)

{

    int i,j,u,ans=0;

    bool f=false;

    u=st;gap[0]=en-st+1;

    for (i=0;i<=n+m+1;++i) cur[i]=point[i];

    while(dis[st]<=en-st)

    {

        f=false;

        for (i=cur[u];i;i=next[i])

        {

            if (edge[i].va&&dis[u]==dis[edge[i].en]+1)

            {

                cur[u]=i;f=true;break;

            }

        }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==en)

            {

                j=inf;

                for (i=en;i!=st;i=edge[pre[i]].st)

                    j=min(j,edge[pre[i]].va);

                ans+=j;

                for (i=en;i!=st;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=j;

                    edge[pre[i]^1].va+=j;

                }

                u=st;

            }

        }

        else

        {

            --gap[dis[u]];

            if (gap[dis[u]]==0) return ans;

            j=en-st+1;

            for (i=point[u];i;i=next[i])

              if (edge[i].va)

                j=min(j,dis[edge[i].en]);

            dis[u]=j+1;cur[u]=point[u];

            ++gap[dis[u]];

            if (u!=st) u=edge[pre[u]].st;

        }

    }

    return ans;

}

int main()

{

    freopen("roundtable.in","r",stdin);

    freopen("roundtable.out","w",stdout);

    

    int i,j,en,summ=0,ans;

    tot=1;

    scanf("%d%d",&m,&n);

    for (i=1;i<=m;++i)

    {

        scanf("%d",&ri[i]);

        summ+=ri[i];

    }

    for (i=1;i<=n;++i)

    {

        scanf("%d",&ci[i]);

        ni[i]=m+i;

    }

    en=n+m+1;

    for (i=1;i<=m;++i)

      add(0,i,ri[i]);

    for (i=1;i<=n;++i)

      add(ni[i],en,ci[i]);

    for (i=1;i<=m;++i)

      for (j=1;j<=n;++j)

        add(i,ni[j],1);

    ans=sap(0,en);

    if (ans!=summ) printf("0\n");

    else

    {

        printf("1\n");

        for (i=1;i<=m;++i)

        {

            for (j=point[i];j;j=next[j])

                if (!edge[j].va) printf("%d ",edge[j].en-m);

            printf("\n");

        }

    }

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs727太空飞行计划

题目大意:有m项实验,n个器材,一直每个实验的价值和器材的花费,以及每个实验所需要的器材,求可获得的最大价值。

思路:最大权闭合子图。读入比较麻烦,然后就是建图跑最大权闭合子图,最后又是一个麻烦的东西:输出方案。基于以前对最大权闭合子图做法的理解,发现如果一个点和源点的连边仍有流量,就说明这个点在答案的集合里,然后我们就要找到所有这样的点以及他们的器材(dfs了一下,这样能把走过的所有边都找到),然后就是按顺序从小到大输出就可以了。

注意:最近做到一些网络流输出方案的题目,要分析流量和答案之间的关系,然后找到相应的方案。

#include<iostream>

#include<cstdio>

#include<cstring>

#define inf 2100000000

using namespace std;

struct use{

    int st,en,va;

}edge[50000]={0};

int dis[210]={0},gap[210]={0},cur[210]={0},pre[210]={0},ni[101]={0},next[50000]={0},point[210]={0},

    exp[101][101]={0},cm[101]={0},cn[101]={0},tot;

bool visit[210]={0};

void add(int st,int en,int va)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;

}

int sap(int st,int en)

{

    int i,j,u,ans=0;

    bool f=false;

    for (i=st;i<=en;++i) cur[i]=point[i];

    gap[0]=en-st+1;u=st;

    while(dis[st]<=en-st)

    {

        f=false;

        for (i=cur[u];i;i=next[i])

        {

            if (edge[i].va&&dis[edge[i].en]+1==dis[u])

            {

                f=true;cur[u]=i;break;

            }

        }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==en)

            {

                j=inf;

                for (i=en;i!=st;i=edge[pre[i]].st)

                  j=min(j,edge[pre[i]].va);

                ans+=j;

                for (i=en;i!=st;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=j;

                    edge[pre[i]^1].va+=j;

                }

                u=st;

            }

        }

        else

        {

            --gap[dis[u]];if (gap[dis[u]]==0) return ans;

            j=en-st+1;

            for (i=point[u];i;i=next[i])

              if (edge[i].va)

                j=min(j,dis[edge[i].en]);

            dis[u]=j+1;++gap[dis[u]];cur[u]=point[u];

            if (u!=st) u=edge[pre[u]].st;

        }

    }

    return ans;

}

void find(int x)

{

    int i,j;

    visit[x]=true;

    for (i=point[x];i;i=next[i])

        if (edge[i].va&&!visit[edge[i].en]) find(edge[i].en);

}

int main()

{

    freopen("shuttle.in","r",stdin);

    freopen("shuttle.out","w",stdout);

    

    int n,m,i,j,ans=0,en;

    char ch;

    tot=1;

    scanf("%d%d",&m,&n);

    for (i=1;i<=m;++i)

    {

        scanf("%d",&cm[i]);

        j=0;

        while(scanf("%c",&ch)==1)

        {

            if (ch=='\r'||ch=='\n') 

            {

               if (j!=0)

               {

                   ++exp[i][0];exp[i][exp[i][0]]=j;

               }

               break;

            }

            if (ch==' '&&j!=0)

            {

                ++exp[i][0];

                exp[i][exp[i][0]]=j;

                j=0;

            }

            else

            {

                if (ch!=' ')

                  j=j*10+ch-'0';

            }

        }

    }

    en=n+m+1;

    for (i=1;i<=n;++i)

    {

        scanf("%d",&cn[i]);

        ni[i]=i+m;

    }

    for (i=1;i<=m;++i) 

    {

       add(0,i,cm[i]);ans+=cm[i];

       for (j=1;j<=exp[i][0];++j)

         add(i,ni[exp[i][j]],inf);

    }

    for (i=1;i<=n;++i) 

       add(ni[i],en,cn[i]);

    ans-=sap(0,en);

    find(0);

    for (i=1;i<=m;++i) 

      if (visit[i]) printf("%d ",i);

    printf("\n");

    for (i=1;i<=n;++i)

      if (visit[ni[i]]) printf("%d ",i);

    printf("\n%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

cogs731最长递增子序列

题目大意:求最长子序列长度,以及原串中能取出多少个(一个元素只能用一遍),当第1个和第n个能用多次的时候能取出多少个。

思路:先dp求出第一个答案(竟然写错了这个交了两遍。。。)然后进行费用流,如果一次增广出的费用等于第一个答案,那么就累加答案,对于第二个和第三个的建图略有不同。第二个当中从源点向每个点、每个点向汇点、每个点向后面>=这个点的点连流量1费用1的边,然后跑最大费用;第三个当中如果是第一个点,就从源点向它连边流量为正无穷,如果为n就向汇点连边流量为正无穷,照样跑。

看到榜上大神的做法:根据第一次dp出来的答案,拆点,连边流量为1,保证一个点只用一次,如果f[i]==1,从s向i连边,如果f[i]==s1,就从i’向t连边,如果f[i]能由f[j]更新,就从j'点向i连边,跑出来的最大流就是第二个答案;在相应的改变流量就是第三个了。

#include<iostream>

#include<cstdio>

#include<cstring>

#define inf 2100000000

#define len 1000000

using namespace std;

struct use{

    int st,en,va,co;

}edge[500000]={0};

int dis[510]={0},a[502]={0},pre[502]={0},next[500000]={0},point[502]={0},aa[501]={0},f[501]={0},

    que[1000001]={0},s1=0,tot;

bool visit[502]={false};

void add(int st,int en,int va,int co)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co;

}

bool sap(int st,int en,int &ss)

{

    int x,y,head,tail;

    memset(visit,false,sizeof(visit));

    memset(dis,128,sizeof(dis));

    dis[st]=0;head=0;tail=1;que[tail]=st;visit[st]=true;a[st]=inf;

    while(head!=tail)

    {

        head=head%len+1;

        x=que[head];visit[x]=false;

        for (y=point[x];y;y=next[y])

        {

            if (edge[y].va&&dis[x]+edge[y].co>dis[edge[y].en])

            {

                dis[edge[y].en]=dis[x]+edge[y].co;

                pre[edge[y].en]=y;

                a[edge[y].en]=min(a[x],edge[y].va);

                if (!visit[edge[y].en])

                {

                    visit[edge[y].en]=true;

                    tail=tail%len+1;que[tail]=edge[y].en;

                }

            }

        }

    }

    if (dis[en]==dis[509]) return false;

    if (dis[en]==s1) ++ss;

    for (x=en;x!=st;x=edge[pre[x]].st)

    {

        edge[pre[x]].va-=a[en];

        edge[pre[x]^1].va+=a[en];

    }

    return true;

}

int main()

{

    freopen("alis.in","r",stdin);

    freopen("alis.out","w",stdout);

    

    int n,i,j,s2=0,s3=0;

    scanf("%d",&n);

    tot=1;

    for (i=1;i<=n;++i)

    {

        scanf("%d",&aa[i]);

        for (j=1;j<i;++j)

            if (aa[j]<=aa[i]&&f[j]>=f[i]) f[i]=f[j];

        ++f[i];

        s1=max(s1,f[i]);

    }

    printf("%d\n",s1);

    for (i=1;i<=n;++i)

    {

        add(0,i,1,1);

        for (j=i+1;j<=n;++j)

            if (aa[i]<=aa[j]) add(i,j,1,1);

        add(i,n+1,1,0);

    }

    while(sap(0,n+1,s2));

    printf("%d\n",s2);

    memset(next,0,sizeof(next));

    memset(point,0,sizeof(point));

    tot=1;

    for (i=1;i<=n;++i)

    {

        if (i==1) add(0,i,inf,1);

        else add(0,i,1,1);

        for (j=i+1;j<=n;++j)

          if (aa[i]<=aa[j]) add(i,j,1,1);

        if (i==n) add(i,n+1,inf,0);

        else add(i,n+1,1,0);

    }

    while(sap(0,n+1,s3));

    printf("%d\n",s3);

    

    fclose(stdin);

    fclose(stdout);

}
费用流

 

cogs741负载平衡

题目大意:环状排列的n个仓库,每一个能向相邻两个输送货物,求n个仓库储量一致的时候最小的代价。

思路:延用了之前很多题目的思路,将这些点同化。求出平均数k之后用aa[i]-k,如果>0就从源点连边,如果<0就向汇点连边,流量就是相应的差费用0。相邻的两点之间连边流量正无穷,费用为1。然后跑费用流,就是答案了。(竟然写错了spfa,队首元素取出后一定要标记置反!!!)

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 1000000

#define inf 2100000000

using namespace std;

struct use{

    int st,en,va,co;

}edge[10000];

int dis[110],a[110],pre[110]={0},que[1000001]={0},next[10000]={0},point[110]={0},aa[101]={0},tot,ans=0;

bool visit[110]={false};

void add(int st,int en,int va,int co)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co;

}

bool sap(int st,int en)

{

    int x,y,head,tail;

    memset(visit,false,sizeof(visit));

    memset(dis,127,sizeof(dis));

    dis[st]=0;head=0;tail=1;que[1]=st;visit[st]=true;a[st]=dis[109];

    while(head!=tail)

    {

        head=head%len+1;

        x=que[head];visit[x]=false;

        for (y=point[x];y;y=next[y])

        {

            if (edge[y].va&&dis[edge[y].en]>dis[x]+edge[y].co)

            {

                dis[edge[y].en]=dis[x]+edge[y].co;

                a[edge[y].en]=min(a[x],edge[y].va);

                pre[edge[y].en]=y;

                if (!visit[edge[y].en])

                {

                    visit[edge[y].en]=true;

                    tail=tail%len+1;que[tail]=edge[y].en;

                }

            }

        }

    }

    if (dis[en]==dis[109]) return false;

    ans+=dis[en]*a[en];

    for (x=en;x!=st;x=edge[pre[x]].st)

    {

        edge[pre[x]].va-=a[en];

        edge[pre[x]^1].va+=a[en];

    }

    return true;

}

int main()

{

    freopen("overload.in","r",stdin);

    freopen("overload.out","w",stdout);

    

    int n,i,j,k=0,en;

    scanf("%d",&n);en=n+1;tot=1;

    for (i=1;i<=n;++i)

    {

        scanf("%d",&aa[i]);

        k+=aa[i];

    }

    k/=n;

    for (i=1;i<=n;++i)

    {

        aa[i]-=k;

        if (aa[i]>0) add(0,i,aa[i],0);

        if (aa[i]<0) add(i,en,-aa[i],0);

        if (i<n)

        {

            add(i,i+1,inf,1);add(i+1,i,inf,1);

        }

        else

        {

            add(1,n,inf,1);add(n,1,inf,1);

        }

    }

    while(sap(0,en));

    printf("%d\n",ans);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

bzoj3993星际战争

题目大意:有n个敌人,m个武器,每种武器有特定的攻击敌人,伤害不同,求最短消灭所有敌人的时间。

思路:因为有小数,我们乘一个1000000,到整数上,然后二分答案,跑网络流(每种武器在二分时间的最多的伤害已知,如果到汇点满流,就是可行)。

        注意longlong和int之间一些强转。。。

#include<iostream>

#include<cstdio>

#include<cstring>

#define inf 1000000

#define linf 9223372036854775806LL

using namespace std;

struct use{

    int st,en;

    long long va;

}edge[10000]={0};

int a[100]={0},b[100]={0},tot,map[100][100],bb[100]={0},ba[100]={0},n,m,point[200]={0},next[10000]={0},

    gap[200]={0},cur[200]={0},dis[200]={0},pre[200]={0};

void add(int st,int en,long long va)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;

}

long long sap(int st,int en)

{

    int i,j,u;

    long long minn,ans=0;

    bool f=false;

    memset(gap,0,sizeof(gap));

    memset(dis,0,sizeof(dis));

    memset(pre,0,sizeof(pre));

    gap[0]=en-st+1;u=st;

    for (i=st;i<=en;++i) cur[i]=point[i];

    while(dis[st]<en-st+1)

    {

        f=false;

        for (i=cur[u];i;i=next[i])

        {

            if (edge[i].va&&dis[edge[i].en]+1==dis[u])

            {

                f=true;cur[u]=i;break;

            }

        }

        if (f)

        {

            pre[edge[i].en]=i;u=edge[i].en;

            if (u==en)

            {

                minn=linf;

                for (i=en;i!=st;i=edge[pre[i]].st)

                    minn=min(minn,edge[pre[i]].va);

                ans+=minn;

                for (i=en;i!=st;i=edge[pre[i]].st)

                {

                    edge[pre[i]].va-=minn;

                    edge[pre[i]^1].va+=minn;

                }

                u=st;

            }

        }

        else

        {

            --gap[dis[u]];

            if (!gap[dis[u]]) return ans;

            minn=en-st+1;

            for (i=point[u];i;i=next[i])

              if (edge[i].va&&dis[edge[i].en]<minn) minn=dis[edge[i].en];

            cur[u]=point[u];

            dis[u]=minn+1;++gap[dis[u]];

            if (u!=st) u=edge[pre[u]].st;

        }

    }

    return ans;

}

bool judge(long long tim,int en)

{

    int i,j;

    long long sum=0,k;

    tot=1;

    memset(point,0,sizeof(point));

    memset(next,0,sizeof(next));

    for (i=1;i<=m;++i)

        add(1,bb[i],tim*(long long)b[i]);

    for (i=1;i<=m;++i)

      for (j=1;j<=n;++j)

          if (map[i][j]) add(bb[i],ba[j],linf);

    for (j=1;j<=n;++j)

    {

      add(ba[j],en,(long long)inf*(long long)a[j]);

      sum+=(long long)inf*(long long)a[j];

    }

    k=sap(1,en);

    if (k==sum) return true;

    else return false;

}

int main()

{

    int i,j,sum=0,minn,en;

    long long ll,rr,mid;

    double ans;

    scanf("%d%d",&n,&m);

    for (i=1;i<=n;++i) 

    {

       scanf("%d",&a[i]);

       sum+=a[i];

       ba[i]=m+i+1;

    }

    minn=2100000000;

    for (j=1;j<=m;++j) 

    {

        scanf("%d",&b[j]);

        minn=min(minn,b[j]);

        bb[j]=j+1;

    }

    en=2+m+n;

    for (i=1;i<=m;++i)

      for (j=1;j<=n;++j)

        scanf("%d",&map[i][j]);

    ll=0;rr=(long long)(sum/minn)*(long long)inf;

    while(ll<rr)

    {

        mid=(ll+rr)/2;

        if (judge(mid,en)) rr=mid;

        else ll=mid+1;

    }

    ans=ll*1.0/inf;

    printf("%.6f\n",ans);

}
View Code

 

cogs410 noi2009植物大战僵尸

题目大意:n*m的方格内有植物,他们可以保护其他植物,也有收益或代价,求收益最大值。

思路:比较明显的最大权闭合子图,但是有一些细节问题。我们按保护关系连边的时候有可能会有环,我们得用拓扑序判一下环。同时有一个隐含条件就是后一列的植物保护同行前一列的植物。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define maxnode 1000

#define inf 2100000000

using namespace std;

int point[maxnode]={0},next[1000000]={0},tot,cur[maxnode]={0},gap[maxnode]={0},dis[maxnode]={0},pre[maxnode]={0},

    map[maxnode][maxnode]={0},wi[maxnode],id[50][50]={0},du[maxnode]={0},zhan[maxnode]={0},st[maxnode*2],

    en[1000000],va[1000000],n,m;

bool visit[maxnode]={false};

void add(int stt,int enn,int vaa)

{

    ++tot;next[tot]=point[stt];point[stt]=tot;en[tot]=enn;va[tot]=vaa;st[tot]=stt;

    ++tot;next[tot]=point[enn];point[enn]=tot;en[tot]=stt;va[tot]=0;st[tot]=enn;

}

int sap(int stt,int enn)

{

    int i,j,u,ans=0;

    bool ff;

    u=stt;gap[0]=enn-stt+1;

    for (i=0;i<=n+m+1;++i) cur[i]=point[i];

    while(dis[stt]<enn-stt+1)

    {

        ff=false;

        for (i=cur[u];i;i=next[i])

        {

            if (va[i]&&dis[en[i]]+1==dis[u])

            {

                ff=true;cur[u]=i;break;

            }

        }

        if (ff)

        {

            pre[en[i]]=i;u=en[i];

            if (u==enn)

            {

                j=inf;

                for (i=u;i!=stt;i=st[pre[i]])

                  j=min(j,va[pre[i]]);

                ans+=j;

                for (i=u;i!=stt;i=st[pre[i]])

                {

                    va[pre[i]]-=j;va[pre[i]^1]+=j;

                }

                u=stt;

            }

        }

        else

        {

            --gap[dis[u]];

            if (!gap[dis[u]]) return ans;

            j=enn-stt+1;

            for (i=point[u];i;i=next[i])

              if (va[i]&&dis[en[i]]<j) j=dis[en[i]];

            dis[u]=j+1;++gap[dis[u]];

            cur[u]=point[u];

            if (u!=stt) u=st[pre[u]];

        }

    }

    return ans;

}

int main()

{

    freopen("pvz.in","r",stdin);

    freopen("pvz.out","w",stdout);

    

    int i,j,k,w,x,y,top=0,sum=0;

    scanf("%d%d",&n,&m);

    for (i=0;i<n;++i)

      for (j=0;j<m;++j)

      {

          id[i][j]=i*m+j+1;

          scanf("%d%d",&wi[id[i][j]],&w);

          for (k=1;k<=w;++k)

          {

              scanf("%d%d",&x,&y);

              map[id[i][j]][++map[id[i][j]][0]]=x*m+y+1;

              ++du[x*m+y+1];

          }

          if (j) 

        {

            map[id[i][j]][++map[id[i][j]][0]]=id[i][j]-1;

            ++du[id[i][j]-1];

        }

      }

    for (i=1;i<=n*m;++i)

      if (!du[i]) zhan[++top]=i;

    while(top)

    {

        i=zhan[top];visit[i]=true;--top;

        for (j=1;j<=map[i][0];++j)

        {

            --du[map[i][j]];

            if (!du[map[i][j]]) zhan[++top]=map[i][j];

        }

    }

    tot=1;

    for (i=1;i<=n*m;++i)

    {

        if (visit[i])

        {

          if (wi[i]>0)

          {

           add(0,i,wi[i]);sum+=wi[i];

          }

          if (wi[i]<0) add(i,n*m+1,-wi[i]);

        }

    }

    for (i=1;i<=n*m;++i)

      for (j=1;j<=map[i][0];++j)

          if (visit[i]&&visit[map[i][j]]) add(map[i][j],i,inf);

    printf("%d\n",sum-sap(0,n*m+1));

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

 

bzoj1001 bjoi2006狼抓兔子

题目大意:网格的边上有一些权值,求从(1,1)到(n,m)的最小割。

思路:这里的n、m都非常大,直接跑网络流肯定会tle,所以我们要根据这个题目的特殊性质进行优化(这一部分看了08年队爷的论文)。根据这个题目中的描述,这是一个平面图,我们建立这个平面图的对偶图(从s->t连一条边,就又多出一个平面,这个平面作为起点,无穷面作为终点)(对于一个两个面间的边,就把这两个面的点连边;对于一个面中的边,就从这个面的点连一条回边)。对偶图中有一些性质:(1)对偶图中的点是平面图中的面,对偶图中的面是平面图中的点,对偶图中的边数等于平面图的边数;(2)对偶图的最短路就是平面图中的最小割。所以我们只需要跑一遍最短路就可以了。

#include<iostream>

#include<cstdio>

#include<cstring>

#define len 2000000

using namespace std;

struct use{

    int en,va;

}edge[6000000]={0};

int point[2000010]={0},next[6000010]={0},dis[2000010],que[2000010]={0},tot=0;

bool visit[2000010]={0};

void add(int st,int en,int va)

{

    ++tot;next[tot]=point[st];point[st]=tot;

    edge[tot].en=en;edge[tot].va=va;

    ++tot;next[tot]=point[en];point[en]=tot;

    edge[tot].en=st;edge[tot].va=va;

}

int spfa(int st,int en)

{

    int i,j,head,tail,u;

    memset(dis,127,sizeof(dis));

    head=tail=0;que[++tail]=st;

    visit[st]=true;dis[st]=0;

    while(head!=tail)

    {

        head=head%len+1;

        u=que[head];visit[u]=false;

        for (i=point[u];i;i=next[i])

        {

            if (dis[edge[i].en]>dis[u]+edge[i].va)

            {

                dis[edge[i].en]=dis[u]+edge[i].va;

                if (!visit[edge[i].en])

                {

                    visit[edge[i].en]=true;

                    tail=tail%len+1;

                    que[tail]=edge[i].en;

                }

            }

        }

    }

    return dis[en];

}

int main()

{

    int n,m,i,j,st,en,ans,k,u,v;

    scanf("%d%d",&n,&m);

    if (n==1||m==1)

    {

        ans=0x7fffffff;

        if (n==1)

        {

          for (i=1;i<m;++i)

          {

              scanf("%d",&j);ans=min(ans,j);

          }

          

        }

        else

        {

            for (i=1;i<n;++i)

            {

                scanf("%d",&j);ans=min(ans,j);

            }

        }

        if (n==1&&m==1) ans=0;

    }

    else

    {

        st=0;en=(2*(n-1)*(m-1))+1;

        for (i=1;i<=n;++i)

          for (j=1;j<m;++j)

          {

               scanf("%d",&k);

               u=((i-2)*(m-1)+j)*2-1;

               v=((i-1)*(m-1)+j)*2;

               if (u<0) u=0;

               if (v>en) v=en;

               add(u,v,k);

          }

        for (i=1;i<n;++i)

          for (j=1;j<=m;++j)

          {

              scanf("%d",&k);

              if (j==1)

              {

                  u=en;v=((i-1)*(m-1)+j)*2-1;

              }

              else

              {

                if (j==m)

                {

                    u=((i-1)*(m-1)+j-1)*2;v=0;

                }

                else

                {

                    u=((i-1)*(m-1)+j)*2-1;v=u-1;

                }

            }

              add(u,v,k);

          }

        for (i=1;i<n;++i)

          for (j=1;j<m;++j)

          {

              scanf("%d",&k);

              u=((i-1)*(m-1)+j)*2-1;v=u+1;

              add(u,v,k);

          }

        ans=spfa(st,en);

    }

    printf("%d\n",ans);

}
View Code

 

你可能感兴趣的:(网络流)