poj3422 Kaka's Matrix Travels

Kaka's Matrix Travels
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 9504 Accepted: 3867

Description

On an N × N chessboard with a non-negative number in each grid, Kaka starts his matrix travels with SUM = 0. For each travel, Kaka moves one rook from the left-upper grid to the right-bottom one, taking care that the rook moves only to the right or down. Kaka adds the number to SUM in each grid the rook visited, and replaces it with zero. It is not difficult to know the maximum SUM Kaka can obtain for his first travel. Now Kaka is wondering what is the maximum SUM he can obtain after his Kth travel. Note the SUM is accumulative during the K travels.

Input

The first line contains two integers N and K (1 ≤ N ≤ 50, 0 ≤ K ≤ 10) described above. The following N lines represents the matrix. You can assume the numbers in the matrix are no more than 1000.

Output

The maximum SUM Kaka can obtain after his Kth travel.

Sample Input

3 2
1 2 3
0 2 1
1 4 2

Sample Output

15
题意:有一个n*n大小的矩阵,每个位置上都有一个非负整数。卡卡从sum=0开始他的矩阵之旅。每次他可以从矩阵的左上角走到右下角位置,每次移动只能向右或者向下移动。每次移动到每个方格,卡卡将方格中的数字加到sum,并将该位置上的数字替换为0。现在要求他旅行完K次之后,sum的最大值是多少。注意,这个sum的值在这K次旅行中是累加的。

输入:n k 接下来就是n*n的矩阵

思路:这是刚开始学最小费用最大流的问题,借着这一次又复习了一下最大流的问题,这个算法我是看了两天书,之后就是慢慢就想明白了,并且是连着最大流也想明白了。

学过最大流的人都知道,只要你能找到一条s-t的增广路,那么你的流就可以继续增加。一直到不能增加了为止。

而这个最小费用呢,就是在某条边上加上了一个费用,现在我们要使得这个费用最小。之后是我自己的理解,当然比较笼统,但是绝对易于理解。


1.我们知道最大流的流是一定的,取决于那些路的短板。然后是我们在找一条增广路时,因为反正你是要增加一块流的,那么我们必然要确定这一次的花费是最小的,那么之后求得的最大流也必定是最小费用的流的。

2.那么我们要如何获得这个最小费用呢?我们知道只要这条增广路s-t我们选定了,那么通过的流是一定的而且每条边也是相同的,我们要使花费最少,于是便产生一个伴随网络。

总的来说就是:只要你这条边还能有流,就必定有花费,然后我们必定每次会选择增广路花费最少的路来走,也就是伴随网络中的最短路,因为你每次加的花费就是每条边的花费*流,要使花费最少那肯定是选最短路啦,因为别的花费肯定是比它大的。有一块流是花费比它大的,那肯定不是我们要的结果,感觉就是最小生成树的那种思想。


好了,接下来说这道题,怎么建模。

1.拆点,每个位置拆成一个出点和入点,两个点之间连接一条容量为1,就是只能走一次啦,费用为矩阵中该位置上的数值的负值。

    然后再建一条边,容量为k-1,费用为0,因为一个点走过之后是要变成0的。这个就是满足一个数只能取一次,但之后是可以走的。

2.右连边和下连边。连接好出点和入点就可以。费用自然是0,流量自然是k。

3.连接超级源点和超级汇点费用0,流量k。

这样最后走出的流量肯定是k,然后获得是一个花费最小值,当时是负的,要求的结果就是花费最小值得相反数了。

看吧,算法或者定理本身可能并不难,但重点就是变形应用,怎么样去应用建模成这个算法,把一个未知的问题转化为一个已知的问题。

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
const int inf=1<<30;
const int MAXN=5000+10;
int n,m;
int tu[55][55];

struct node
{
    int u,v,w,f;
    int next;
} edge[MAXN<<2];

int cnt,head[MAXN];
void add(int u,int v,int w,int f)
{
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].f=f;
    edge[cnt].next=head[u];
    head[u]=cnt++;
    edge[cnt].u=v;
    edge[cnt].v=u;
    edge[cnt].w=-w;
    edge[cnt].f=0;
    edge[cnt].next=head[v];
    head[v]=cnt++;
}

int pre[MAXN],dis[MAXN];
bool vis[MAXN];
int s,e;
bool spfa()
{
    int i;
    for(i=0; i<=e; ++i)
    {
        pre[i]=-1;
        dis[i]=inf;
        vis[i]=0;
    }
    queue<int>q;
    q.push(s);
    dis[s]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(i=head[u]; i!=-1; i=edge[i].next)
        {
            int v=edge[i].v;
            int w=edge[i].w;
            int f=edge[i].f;
            int t=dis[u]+w;
            if(f>0&&dis[v]>t)
            {
                dis[v]=t;
                pre[v]=i;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    if(pre[e]==-1)return 0;
    return 1;
}
int get_mincost()
{
    int ans=0;
    while(spfa())
    {
        int max_flow=inf;
        int p=pre[e];
        while(p!=-1)
        {
            max_flow=min(max_flow,edge[p].f);
            p=pre[edge[p].u];
        }
        p=pre[e];
        while(p!=-1)
        {
            edge[p].f-=max_flow;
            edge[p^1].f+=max_flow;
            ans+=max_flow*edge[p].w;
            p=pre[edge[p].u];
        }
    }
    return ans;
}
void build()
{
    int i,j;
    cnt=0;
    for(i=0; i<=e; ++i)head[i]=-1;
    //拆点建边
    for(i=0; i<n; ++i)
        for(j=0; j<n; ++j)
        {
            int u=i*n+j;
            add(u*2,u*2+1,-tu[i][j],1);
            add(u*2,u*2+1,0,m-1);
        }
    //向右移动建边
    for(i=0; i<n; ++i)
        for(j=0; j<n-1; ++j)
        {
            int u=i*n+j;
            add(u*2+1,(u+1)*2,0,m);
        }
    //向下移动建边
    for(i=0; i<n-1; ++i)
        for(j=0; j<n; ++j)
        {
            int u=i*n+j;
            add(u*2+1,(u+n)*2,0,m);
        }
    //超级源点
    add(s,0,0,m);
    //超级汇点
    add(s-1,e,0,m);
}



int main()
{
    int i,j;
    while(~scanf("%d%d",&n,&m))
    {
        for(i=0; i<n; ++i)
            for(j=0; j<n; ++j)scanf("%d",&tu[i][j]);
        s=n*n*2;
        e=s+1;
        build();
        printf("%d\n",-get_mincost());
    }
    return 0;
}





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值