• bzoj1001(洛谷P4001)


    Author : hiang

    bzoj1001     洛谷 P4001 

    Time Limit: 15 Sec    Memory Limit: 162 MB

    Description

    现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:

     

    左上角点为(1,1),右下角点为(N,M)(上图中N=3,M=4).有以下三种类型的道路 
    1:(x,y)<==>(x+1,y) 
    2:(x,y)<==>(x,y+1) 
    3:(x,y)<==>(x+1,y+1) 
    道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的. 左上角和右下角为兔子的两个窝,开始时所有的兔子都聚集在左上角(1,1)的窝里,现在它们要跑到右下解(N,M)的窝中去,狼王开始伏击这些兔子.当然为了保险起见,如果一条道路上最多通过的兔子数为K,狼王需要安排同样数量的K只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦.

    Input

    第一行为N,M.表示网格的大小,N,M均小于等于1000.
    接下来分三部分
    第一部分共N行,每行M-1个数,表示横向道路的权值. 
    第二部分共N-1行,每行M个数,表示纵向道路的权值. 
    第三部分共N-1行,每行M-1个数,表示斜向道路的权值. 
    输入文件保证不超过10M

    Output

    输出一个整数,表示参与伏击的狼的最小数量.

    Sample Input

    3 4
    5 6 4
    4 3 1
    7 5 3
    5 6 7 8
    8 7 6 5
    5 5 5
    6 6 6

    Sample Output

    14
     
     
    这里提供两种思路:
    一、最大流/最小割
    学过网络流的不难看出这题符合最大流的条件,首先最重要的部分就是建图,下面提供建图方法:
    一共有n行m列,所以我们可以将第一行的点设为1~m,第二行设为m+1~m*2,依此类推,可以得出起点是1,终点是n*m,按照输入的权值建边,注意因为是无向图,所以要建双向边,不然会WA,建完图跑最大流就可以了。
    不过这题是网格图,边数较多,用朴素的dinic会TLE,所以加了一些玄学的优化
     
    AC代码:
      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define MAXN 1001005
      4 #define MAXM 3004005
      5 #define inf 0x3f3f3f
      6 int n,m,s,t,num_edge=-1;
      7 int head[MAXN],cur[MAXN],dis[MAXN];
      8 struct Edge
      9 {
     10     int to,w,next;
     11 }edge[MAXM*2];
     12 inline int read()//快速读入
     13 {
     14    int s=0,w=1;
     15    char ch=getchar();
     16    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
     17    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
     18    return s*w;
     19 }
     20 void addedge(int from,int to,int w)
     21 {
     22     edge[++num_edge].next=head[from];
     23     edge[num_edge].to=to;
     24     edge[num_edge].w=w;
     25     head[from]=num_edge;
     26 }
     27 bool bfs()
     28 {
     29     memset(dis,0,sizeof(dis));
     30     for(int i=1;i<=n*m;i++)
     31         cur[i]=head[i];
     32     dis[s]=1;
     33     queue<int> q;
     34     q.push(s);
     35     while(!q.empty())
     36     {
     37         int u=q.front();
     38         q.pop();
     39         for(int i=head[u];i!=-1;i=edge[i].next)
     40         {
     41             if(dis[edge[i].to]==0&&edge[i].w)
     42             {
     43                 dis[edge[i].to]=dis[u]+1;
     44                 if(edge[i].to==t)
     45                     return 1;
     46                 q.push(edge[i].to);
     47             }
     48         }
     49     }
     50     return 0;
     51 }
     52 int dfs(int p,int limit)
     53 {
     54     if(p==t)
     55         return limit;
     56     int mi,used=0;
     57     for(int &i=cur[p];i!=-1;i=edge[i].next)//当前弧优化,非常省时
     58     {
     59         if(dis[edge[i].to]==dis[p]+1&&edge[i].w&&(mi=dfs(edge[i].to,min(edge[i].w,limit))))
     60         {
     61             if(mi)
     62             {
     63                 used+=mi;
     64                 limit-=mi;
     65                 edge[i].w-=mi;
     66                 edge[i^1].w+=mi;
     67                 if(!limit)
     68                     return used;
     69             }
     70             else
     71                 dis[p]=-1;//非常重要的优化,表示当前点无法再进行增广
     72         }
     73     }
     74     return used;
     75 }
     76 long long dinic()
     77 {
     78     long long ans=0;
     79     while(bfs())
     80         ans+=dfs(s,inf);
     81     return ans;
     82 }
     83 int main()
     84 {
     85     memset(head,-1,sizeof(head));
     86     int w,i,j;
     87     n=read();
     88     m=read();
     89     s=1;
     90     t=n*m;
     91     for(i=0;i<n;i++)//建横向边
     92     {
     93         for(j=1;j<=m-1;j++)
     94         {
     95             w=read();
     96             addedge(i*m+j,i*m+j+1,w);
     97             addedge(i*m+j+1,i*m+j,w);
     98         }
     99     }
    100     for(i=0;i<n-1;i++)//建纵向边
    101     {
    102         for(j=1;j<=m;j++)
    103         {
    104             w=read();
    105             addedge(i*m+j,(i+1)*m+j,w);
    106             addedge((i+1)*m+j,i*m+j,w);
    107         }
    108     }
    109     for(i=0;i<n-1;i++)//建斜向边
    110     {
    111         for(j=1;j<=m-1;j++)
    112         {
    113             w=read();
    114             addedge(i*m+j,(i+1)*m+j+1,w);
    115             addedge((i+1)*m+j+1,i*m+j,w);
    116         }
    117     }
    118     printf("%lld",dinic());
    119     return 0;
    120 }

     二、平面图转对偶图 求最短路

    先简单解释一下原理:

    平面图:能画在平面上,且各边交点只能为顶点的图

    对偶图:将平面图的各区域抽象成一个点,相邻区域之间连一条边,形成对偶图

    以该题为例:

    因为我们需要有一个起点和一个终点,所以我们要建立一个附加面,即s'所在的面,所得对偶图如上(建图方法不唯一)

    可以发现,从s'到t'的任意一条路都是原图的割,于是这道题就转换成了求s'到t'的最短路,这里我用的spfa,居然比玄学的dinic还慢了一秒多......

    dinic时间太玄学,但建图较简单,求最短路转换成对偶图后建图过程非常繁琐...但是较为保险

    AC代码:

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define inf 0x3f3f3f3f
      4 const int MAXN=2001005;
      5 const int MAXM=6104005;
      6 int n,m,s,t,num_edge=-1;
      7 int head[MAXN];
      8 int dis[MAXN];
      9 bool inq[MAXN];
     10 struct Edge
     11 {
     12     int to,w,next;
     13 }edge[MAXM];
     14 inline int read()
     15 {
     16    int s=0,w=1;
     17    char ch=getchar();
     18    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
     19    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
     20    return s*w;
     21 }
     22 void addedge(int from,int to,int w)//邻接表建图
     23 {
     24     edge[++num_edge].next=head[from];
     25     edge[num_edge].to=to;
     26     edge[num_edge].w=w;
     27     head[from]=num_edge;
     28 }
     29 void spfa()
     30 {
     31     memset(dis,inf,sizeof(dis));
     32     memset(inq,0,sizeof(inq));
     33     dis[s]=0;
     34     queue<int> q;
     35     q.push(s);
     36     inq[s]=1;
     37     while(!q.empty())
     38     {
     39         int u=q.front();
     40         q.pop();
     41         inq[u]=0;
     42         for(int i=head[u];i!=-1;i=edge[i].next)
     43         {
     44             if(dis[edge[i].to]>dis[u]+edge[i].w)
     45             {
     46                 dis[edge[i].to]=dis[u]+edge[i].w;
     47                 if(!inq[edge[i].to])
     48                 {
     49                     q.push(edge[i].to);
     50                     inq[edge[i].to]=1;
     51                 }
     52             }
     53         }
     54     }
     55 }
     56 int main()
     57 {
     58     memset(head,-1,sizeof(head));
     59     int i,j,w,x,y;
     60     scanf("%d%d",&n,&m);
     61     s=(n-1)*(m-1)*2+1;//s不要设成0,亲测会WA
     62     t=(n-1)*(m-1)*2+2;
     63     for(i=1;i<=n;i++)//令人崩溃的建图
     64         for(j=1;j<m;j++)
     65         {
     66             w=read();
     67             x=(2*(i-1)-1)*(m-1)+j;
     68             y=2*(i-1)*(m-1)+j;
     69             if(i==1)
     70                 x=s;
     71             else if(i==n)
     72                 y=t;
     73             addedge(x,y,w);
     74             addedge(y,x,w);
     75         }
     76     for(i=1;i<n;i++)
     77         for(j=1;j<=m;j++)
     78         {
     79             w=read();
     80             x=2*(i-1)*(m-1)+j-1;
     81             y=(2*(i-1)+1)*(m-1)+j;
     82             if(j==1)
     83             {
     84                 x=(2*(i-1)+1)*(m-1)+j;
     85                 y=t;
     86             }
     87             else if(j==m)
     88             {
     89                 y=x;
     90                 x=s;
     91             }
     92             addedge(x,y,w);
     93             addedge(y,x,w);
     94         }
     95     for(i=1;i<n;i++)
     96         for(j=1;j<m;j++)
     97         {
     98             w=read();
     99             x=2*(i-1)*(m-1)+j;
    100             y=(2*(i-1)+1)*(m-1)+j;
    101             addedge(x,y,w);
    102             addedge(y,x,w);
    103         }
    104     spfa();
    105     printf("%d",dis[t]);
    106     return 0;
    107 }
  • 相关阅读:
    c++中单引号和双引号的区别
    C++ 数组
    C++ 输出到文本文件
    C++中文本的读入
    C++ 输入和输出
    C++构造函数和文件组织
    Linux中使用gcc编译文件
    linux下修改gcc编译器版本
    Git--创建与合并分支
    'webpack' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  • 原文地址:https://www.cnblogs.com/CSGOBESTGAMEEVER/p/10976157.html
Copyright © 2020-2023  润新知