HDU 3820 Golden Eggs( 最小割 奇特建图)经典

本文介绍了一道经典的最小割问题——HDU3820 Golden Eggs。通过特殊的建图方式解决该问题,利用SAP算法求得最大流,进而求得最高可能得分。

Golden Eggs

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 501    Accepted Submission(s): 281


Problem Description
There is a grid with N rows and M columns. In each cell you can choose to put a golden or silver egg in it, or just leave it empty. If you put an egg in the cell, you will get some points which depends on the color of the egg. But for every pair of adjacent eggs with the same color, you lose G points if there are golden and lose S points otherwise. Two eggs are adjacent if and only if there are in the two cells which share an edge. Try to make your points as high as possible.
 

Input
The first line contains an integer T indicating the number of test cases.
There are four integers N, M, G and S in the first line of each test case. Then 2*N lines follows, each line contains M integers. The j-th integer of the i-th line Aij indicates the points you will get if there is a golden egg in the cell(i,j). The j-th integer of the (i+N)-th line Bij indicates the points you will get if there is a silver egg in the cell(i,j).

Technical Specification
1. 1 <= T <= 20
2. 1 <= N,M <= 50
3. 1 <= G,S <= 10000
4. 1 <= Aij,Bij <= 10000
 

Output
For each test case, output the case number first and then output the highest points in a line.
 

Sample Input

      
2 2 2 100 100 1 1 5 1 1 4 1 1 1 4 85 95 100 100 10 10 10 10 100 100
 

Sample Output

      
Case 1: 9 Case 2: 225
 

Author
hanshuai
 

Source
题意:有一个n*m的格子。能够放两种颜色我蛋:金蛋和银蛋。假设相邻格子里放同一颜色的蛋:假设为金蛋则降低价值为G,假设银蛋则降低价值为S。接下来n行m列表示放金蛋得到的价值mp1[][],再接下来n行m列表示放银蛋得到的价值mp2[][]。问最大能得到的价值。
解题:vs:源点,vt:汇点。建图:把n*m个格子分成奇偶两部分。每一个点都须要拆成两个点v,v'。奇点v:<vs , v , mp1[i][j]>。<v , v' , INF>。<v' , vt , mp2[i][j]>。与v相邻点u。<v , u' , G>。

偶点u:<vs , u , mp2[i][j]>。<u , u' , INF>,<u' , vt , mp1[i][j]>。

与u相邻点v。<u , v' , S>。这样图建好了,那么答案:mp1[][]+mp2[][] -maxflow。

/*
最大流:SAP算法,与ISAP的区别就是不用预处理
*/
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
#define captype int

const int MAXN = 100010;   //点的总数
const int MAXM = 400010;    //边的总数
const int INF = 1<<30;
struct EDG{
    int to,next;
    captype cap,flow;
} edg[MAXM];
int eid,head[MAXN];
int gap[MAXN];  //每种距离(或可觉得是高度)点的个数
int dis[MAXN];  //每一个点到终点eNode 的最短距离
int cur[MAXN];  //cur[u] 表示从u点出发可流经 cur[u] 号边
int pre[MAXN];

void init(){
    eid=0;
    memset(head,-1,sizeof(head));
}
//有向边 三个參数。无向边4个參数
void addEdg(int u,int v,captype c,captype rc=0){
    edg[eid].to=v; edg[eid].next=head[u];
    edg[eid].cap=c; edg[eid].flow=0; head[u]=eid++;

    edg[eid].to=u; edg[eid].next=head[v];
    edg[eid].cap=rc; edg[eid].flow=0; head[v]=eid++;
}
captype maxFlow_sap(int sNode,int eNode, int n){//n是包含源点和汇点的总点个数。这个一定要注意
    memset(gap,0,sizeof(gap));
    memset(dis,0,sizeof(dis));
    memcpy(cur,head,sizeof(head));
    pre[sNode] = -1;
    gap[0]=n;
    captype ans=0;  //最大流
    int u=sNode;
    while(dis[sNode]<n){   //推断从sNode点有没有流向下一个相邻的点
        if(u==eNode){   //找到一条可增流的路
            captype Min=INF ;
            int inser;
            for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to])    //从这条可增流的路找到最多可增的流量Min
            if(Min>edg[i].cap-edg[i].flow){
                Min=edg[i].cap-edg[i].flow;
                inser=i;
            }
            for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to]){
                edg[i].flow+=Min;
                edg[i^1].flow-=Min;  //可回流的边的流量
            }
            ans+=Min;
            u=edg[inser^1].to;
            continue;
        }
        bool flag = false;  //推断是否能从u点出发可往相邻点流
        int v;
        for(int i=cur[u]; i!=-1; i=edg[i].next){
            v=edg[i].to;
            if(edg[i].cap-edg[i].flow>0 && dis[u]==dis[v]+1){
                flag=true;
                cur[u]=pre[v]=i;
                break;
            }
        }
        if(flag){
            u=v;
            continue;
        }
        //假设上面没有找到一个可流的相邻点。则改变出发点u的距离(也可觉得是高度)为相邻可流点的最小距离+1
        int Mind= n;
        for(int i=head[u]; i!=-1; i=edg[i].next)
        if(edg[i].cap-edg[i].flow>0 && Mind>dis[edg[i].to]){
            Mind=dis[edg[i].to];
            cur[u]=i;
        }
        gap[dis[u]]--;
        if(gap[dis[u]]==0) return ans;  //当dis[u]这样的距离的点没有了。也就不可能从源点出发找到一条增广流路径
                                        //由于汇点到当前点的距离仅仅有一种,那么从源点到汇点必定经过当前点。然而当前点又没能找到可流向的点,那么必定断流
        dis[u]=Mind+1;//假设找到一个可流的相邻点,则距离为相邻点距离+1,假设找不到。则为n+1
        gap[dis[u]]++;
        if(u!=sNode) u=edg[pre[u]^1].to;  //退一条边
    }
    return ans;
}
int main()
{
    int T,n,m,vs,vt,mp1[55][55],mp2[55][55],G,S;
    int dir[4][2]={0,1,0,-1,1,0,-1,0};
    scanf("%d",&T);
    for(int _case=1; _case<=T; ++_case){

        int ans=0;

        scanf("%d%d%d%d",&n,&m,&G,&S);
        for(int i=0; i<n; i++)
            for(int j=0; j<m; j++)
              scanf("%d",&mp1[i][j]) , ans+=mp1[i][j];
        for(int i=0; i<n; i++)
            for(int j=0; j<m; j++)
              scanf("%d",&mp2[i][j]) , ans+=mp2[i][j];

        vs = 2*n*m; vt = vs+1;
        init();
        for(int i=0; i<n; i++)
            for(int j=0; j<m; j++){
                int u=i*m+j;
                  if((i+j)&1){
                     addEdg(vs , u , mp1[i][j]);
                     addEdg(u , u+n*m , INF);
                     addEdg(u+n*m , vt , mp2[i][j]);
                     for(int e=0; e<4; e++)
                     {
                         int ti , tj;
                         ti=i+dir[e][0];
                         tj=j+dir[e][1];
                         if(ti>=0&&ti<n&&tj>=0&&tj<m)
                            addEdg(u , ti*m+tj+n*m , G);
                     }
                  }
                  else{
                    addEdg(vs , u , mp2[i][j]);
                     addEdg(u , u+n*m , INF);
                     addEdg(u+n*m , vt , mp1[i][j]);
                     for(int e=0; e<4; e++)
                     {
                         int ti , tj;
                         ti=i+dir[e][0];
                         tj=j+dir[e][1];
                         if(ti>=0&&ti<n&&tj>=0&&tj<m)
                            addEdg(u , ti*m+tj+n*m , S);
                     }
                  }
            }
        ans-=maxFlow_sap(vs , vt , vt+1);
        printf("Case %d: %d\n",_case , ans);
    }
}


posted on 2017-07-19 14:46 mthoutai 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/mthoutai/p/7205733.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值