[codevs 1227] 方格取数 2

题目描述 Description
 给出一个n*n的矩阵,每一格有一个非负整数Aij,(Aij <= 1000)现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0,这样一共走K次,现在要求K次所达到的方格的数的和最大
输入描述 Input Description

第一行两个数n,k(1<=n<=50, 0<=k<=10)

接下来n行,每行n个数,分别表示矩阵的每个格子的数

输出描述 Output Description
一个数,为最大值
样例输入 Sample Input
3 1
1 2 3
0 2 1
1 4 2
样例输出 Sample Output
11
数据范围及提示 Data Size & Hint
1<=n<=50, 0<=k<=10

这是我学了网络流之后自己写的第一道建模题,虽说这道题也是一道很普通的题。

建图如下:很显然想到把每个点当成点,向下面的点和右边的点连边,至于权值的事情,那就把一个点拆成两个点,分为上点和下点,上点向下点连一条费用为该点点权,容量为1的边(因为每个数只能取一次),但发现取完这个数后这个点还是可以走的,那就让上点再连一条费用为0,容量为正无穷的边(表示可以走很多次)到下点。汇点显然是最后一个点的下点,至于源点的设定,因为题目中有走k次的要求,那就新创一个点,连向起点的上点,费用为0,容量为K,因为每一次取容量为1,所以取K次的意思就相当于走k条增广路。然后就求一遍最大费用流即可。建图还有一个比较烦的就是每个点编号的编排,具体编排方法下文注释上有。

  1 #include<iostream>
  2 #include<cmath>
  3 #include<cstring>
  4 #include<cstdio>
  5 #include<algorithm>
  6 #include<queue>
  7 using namespace std;
  8 typedef long long LL;
  9 inline int read()    
 10 {
 11     int x=0,f=1;char c=getchar();
 12     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
 13     while(isdigit(c)){x=x*10+c-'0';c=getchar();}
 14     return x*f;
 15 }
 16 const int oo=2147000000;
 17 const int maxn=100000;
 18 struct Edge
 19 {
 20     int u,v,f,w,next;
 21     Edge() {}
 22     Edge(int _1,int _2,int _3,int _4,int _5) : u(_1),v(_2),f(_3),w(_4),next(_5) {}
 23 }e[maxn];
 24 int n,k,first[maxn],vis[maxn],dis[maxn],a[100][100],cnt,s,t,ans;
 25 queue<int> Q;
 26 void add(int i,int a,int b,int c,int d)
 27 {
 28     e[i]=Edge(a,b,c,d,first[a]);
 29     first[a]=i;
 30 }
 31 void addEdge(int i,int a,int b,int c,int d){add(2*i,a,b,c,d);add(2*i+1,b,a,0,-d);}
 32 bool spfa()
 33 {
 34     memset(vis,0,sizeof(vis));
 35     for(int i=0;i<=2*n*n;i++)dis[i]=-oo;
 36     while(Q.size())Q.pop();
 37     dis[t]=0;vis[t]=1;Q.push(t);
 38     while(Q.size())
 39     {
 40         int now=Q.front();Q.pop();
 41         for(int i=first[now];i!=-1;i=e[i].next)
 42             if(dis[now]+e[i^1].w>dis[e[i].v] && e[i^1].f)
 43             {
 44                 dis[e[i].v]=dis[now]+e[i^1].w; 
 45                 if(!vis[e[i].v])
 46                 {
 47                     vis[e[i].v]=1;
 48                     Q.push(e[i].v);
 49                 }
 50             }
 51         vis[now]=0;
 52     }
 53     return dis[s]!=-oo;
 54 }
 55 int dfs(int x,int flow)
 56 {
 57     vis[x]=1;
 58     if(x==t)return flow;
 59     int now,used=0;
 60     for(int i=first[x];i!=-1;i=e[i].next)
 61         if(dis[e[i].v]==dis[x]-e[i].w && e[i].f && !vis[e[i].v])
 62         {
 63             now=flow-used;
 64             now=dfs(e[i].v,min(now,e[i].f));
 65             ans+=now*e[i].w;
 66             e[i].f-=now;e[i^1].f+=now;
 67             used+=now;
 68             if(used==flow)return flow;
 69         }
 70     return used;
 71 }
 72 void zkw()
 73 {
 74     while(spfa())
 75     {
 76         vis[t]=1;
 77         while(vis[t])
 78         {
 79             memset(vis,0,sizeof(vis));
 80             dfs(s,oo);
 81         } 
 82     }
 83 }
 84 int main()
 85 {
 86     memset(first,-1,sizeof(first));
 87     n=read();k=read();
 88     for(int i=0;i<n;i++)for(int j=0;j<n;j++)a[i][j]=read();
 89     for(int i=0;i<n;i++)
 90         for(int j=0;j<n;j++)
 91         {
 92             addEdge(cnt,2*(i*n+j),2*(i*n+j)+1,1,a[i][j]);cnt++;
 93             addEdge(cnt,2*(i*n+j),2*(i*n+j)+1,oo,0);cnt++;
 94             if(j!=n-1)addEdge(cnt,2*(i*n+j)+1,2*(i*n+j+1),oo,0);cnt++;//向右边 
 95             if(i!=n-1)addEdge(cnt,2*(i*n+j)+1,2*((i+1)*n+j),oo,0);cnt++;//下面 
 96         }
 97     addEdge(cnt,2*n*n,0,k,0);cnt++;
 98     s=2*n*n;t=2*n*n-1;
 99     zkw();
100     printf("%d",ans);
101     return 0;
102 }
103 /*
104 建图:
105 原图中a[i][j],对应编号为i*n+j,拆成两个点,一个是2*(i*n+j),另一个是2*(i*n+j)+1
106  (i,j)上点向下点连两条边,下点向下面和右面连一条边 
107 源点 2*n*n 向0,0连一条边 
108 汇点 2*n*n-1 
109 */

 

posted @ 2017-01-15 21:01  小飞淙的云端  阅读(649)  评论(0编辑  收藏  举报