HDU 3820 Golden Eggs (SAP | Dinic)

Golden Eggs

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


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
 

 

Recommend
lcy
 

 还是SAP快:

大致题意:
    给出一块n*m的区域,已知区域中的每个格子都可以放金蛋或者是银弹,已知每个格子放金蛋或者是放银弹时可以得到的收益,分别用矩阵map1[][],map2[][]表示。如果相邻的两个格子放的蛋相同的话会扣去一定的收益,金蛋的话会扣去g,银弹s。求收益的最大值是多少。
 
大致思路:
    首先,对于某个确定的位置,只能选择金蛋或者银弹的其中一个放上去,从这里可以想到和二分图最大点权独立集有关。其次,如果相邻的位置放的蛋相同的话需要付出一定的花费这个又和hdu 3657:Game有些许相似之处。所以我们这么构图:
 
按照黑白染色把矩阵中的位置分为A,B两个集合,每个集合中的位置拆为k,k'两个点。对于A集合,从源点向k连边,容量为它放金蛋是的收获,从k再向k'连边容量设为inf,从k'连到汇点,容量为其放银弹时的收益。这样设的目的就是为了使得割必须在源点到k或者k'到汇点上。   
对于B集合,我们做一些改变,把从源点到k的容量设为放银弹的收益,k'到汇点的容量设为其放金蛋的收益,k和k'之间容量为inf的边不变。至于为什么看下一步。
然后再接下来一步加边就是关键了!我们知道如果A集合中的金蛋和B集合中的金蛋相邻的话会付出一定的话费,为了表示设A集合中的某位置a是金蛋,我们会连接源点->a。为了表示B集合中位置b是金蛋的,我们会连接b'->汇点。好的,在这里我们假设b位置和a相邻。为了表示a位置和b位置同时放金蛋需要G的花费,我们连接a->b'并把其容量设为G,对于银弹我们做同样的处理。至于为什么,好好把hdu3567刷掉就全明白了
接下来,用所有的map1[i][j]+map2[i][j]-最小割得到的就是答案。
 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>

using namespace std;

const int VM=101000;
const int EM=500100;
const int INF=0x3f3f3f3f;

struct Edge{
    int to,nxt;
    int cap;
}edge[EM<<1];

int n,m,G,S,cnt,head[VM],src,des,map1[110][110],map2[110][110];
int dep[VM],gap[VM],cur[VM],aug[VM],pre[VM];

void addedge(int cu,int cv,int cw){
    edge[cnt].to=cv;  edge[cnt].cap=cw;  edge[cnt].nxt=head[cu];
    head[cu]=cnt++;
    edge[cnt].to=cu;  edge[cnt].cap=0;   edge[cnt].nxt=head[cv];
    head[cv]=cnt++;
}

int SAP(int n){
    int max_flow=0,u=src,v;
    int id,mindep;
    aug[src]=INF;
    pre[src]=-1;
    memset(dep,0,sizeof(dep));
    memset(gap,0,sizeof(gap));
    gap[0]=n;
    for(int i=0;i<=n;i++)
        cur[i]=head[i]; // 初始化当前弧为第一条弧
    while(dep[src]<n){
        int flag=0;
        if(u==des){
            max_flow+=aug[des];
            for(v=pre[des];v!=-1;v=pre[v]){     // 路径回溯更新残留网络
                id=cur[v];
                edge[id].cap-=aug[des];
                edge[id^1].cap+=aug[des];
                aug[v]-=aug[des];   // 修改可增广量,以后会用到
                if(edge[id].cap==0) // 不回退到源点,仅回退到容量为0的弧的弧尾
                    u=v;
            }
        }
        for(int i=cur[u];i!=-1;i=edge[i].nxt){
            v=edge[i].to;    // 从当前弧开始查找允许弧
            if(edge[i].cap>0 && dep[u]==dep[v]+1){  // 找到允许弧
                flag=1;
                pre[v]=u;
                cur[u]=i;
                aug[v]=min(aug[u],edge[i].cap);
                u=v;
                break;
            }
        }
        if(!flag){
            if(--gap[dep[u]]==0)    // gap优化,层次树出现断层则结束算法
                break;
            mindep=n;
            cur[u]=head[u];
            for(int i=head[u];i!=-1;i=edge[i].nxt){
                v=edge[i].to;
                if(edge[i].cap>0 && dep[v]<mindep){
                    mindep=dep[v];
                    cur[u]=i;   // 修改标号的同时修改当前弧
                }
            }
            dep[u]=mindep+1;
            gap[dep[u]]++;
            if(u!=src)  // 回溯继续寻找允许弧
                u=pre[u];
        }
    }
    return max_flow;
}

int main(){

    //freopen("input.txt","r",stdin);

    int t,cases=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d%d",&n,&m,&G,&S);
        cnt=0;
        memset(head,-1,sizeof(head));
        src=0; des=n*m*2+1;
        int sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map1[i][j]);
                sum+=map1[i][j];
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map2[i][j]);
                sum+=map2[i][j];
            }
        int tmp;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                tmp=(i-1)*m+j;
                if((i+j)%2==0){
                    addedge(src,tmp,map1[i][j]);
                    addedge(tmp,tmp+n*m,INF);
                    addedge(tmp+n*m,des,map2[i][j]);

                    if(i>1) addedge(tmp,tmp-m+n*m,G);
                    if(i<n) addedge(tmp,tmp+m+n*m,G);
                    if(j>1) addedge(tmp,tmp-1+n*m,G);
                    if(j<m) addedge(tmp,tmp+1+n*m,G);

                }else{
                    addedge(src,tmp,map2[i][j]);
                    addedge(tmp,tmp+n*m,INF);
                    addedge(tmp+n*m,des,map1[i][j]);

                    if(i>1) addedge(tmp,tmp-m+n*m,S);
                    if(i<n) addedge(tmp,tmp+m+n*m,S);
                    if(j>1) addedge(tmp,tmp-1+n*m,S);
                    if(j<m) addedge(tmp,tmp+1+n*m,S);
                }
            }
        }
        printf("Case %d: %d\n",++cases,sum-SAP(des+1));
    }
    return 0;
}

 

 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>

using namespace std;

const int VM=101000;
const int EM=500100;
const int INF=0x3f3f3f3f;

struct Edge{
    int to,nxt;
    int cap;
}edge[EM<<1];

int n,m,G,S,cnt,head[VM],src,des;
int map1[110][110],map2[110][110],dep[VM];    //dep[i]表示当前点到起点src的层数

void addedge(int cu,int cv,int cw){
    edge[cnt].to=cv;  edge[cnt].cap=cw;  edge[cnt].nxt=head[cu];
    head[cu]=cnt++;
    edge[cnt].to=cu;  edge[cnt].cap=0;   edge[cnt].nxt=head[cv];
    head[cv]=cnt++;
}

int BFS(){      // 重新建图(按层数建图)
    queue<int> q;
    while(!q.empty())
        q.pop();
    memset(dep,-1,sizeof(dep));
    dep[src]=0;
    q.push(src);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=edge[i].nxt){
            int v=edge[i].to;
            if(edge[i].cap>0 && dep[v]==-1){      // 如果可以到达且还没有访问
                dep[v]=dep[u]+1;
                q.push(v);
            }
        }
    }
    return dep[des]!=-1;
}

int DFS(int u,int minx){    // 查找路径上的最小的流量
    if(u==des)
        return minx;
    int tmp;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(edge[i].cap>0 && dep[v]==dep[u]+1 && (tmp=DFS(v,min(minx,edge[i].cap)))){
            edge[i].cap-=tmp;     //正向减少
            edge[i^1].cap+=tmp;     //反向增加
            return tmp;
        }
    }
    dep[u]=-1;  //这一句作用无穷大,不在TLE。。。。。。。。。。。
    return 0;
}

int Dinic(){
    int ans=0,tmp;
    while(BFS()){
        while(1){
            tmp=DFS(src,INF);
            if(tmp==0)
                break;
            ans+=tmp;
        }
    }
    return ans;
}

int main(){

    //freopen("input.txt","r",stdin);

    int t,cases=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d%d",&n,&m,&G,&S);
        cnt=0;
        memset(head,-1,sizeof(head));
        src=0; des=n*m*2+1;
        int sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map1[i][j]);
                sum+=map1[i][j];
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map2[i][j]);
                sum+=map2[i][j];
            }
        int tmp;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                tmp=(i-1)*m+j;
                if((i+j)%2==0){
                    addedge(src,tmp,map1[i][j]);
                    addedge(tmp,tmp+n*m,INF);
                    addedge(tmp+n*m,des,map2[i][j]);
                    //
                    if(i!=1)addedge(tmp,tmp-m+n*m,G);
                    if(i!=n)addedge(tmp,tmp+m+n*m,G);
                    if(j!=1)addedge(tmp,tmp-1+n*m,G);
                    if(j!=m)addedge(tmp,tmp+1+n*m,G);
                    //
                }
                else{
                    addedge(src,tmp,map2[i][j]);
                    addedge(tmp,tmp+n*m,INF);
                    addedge(tmp+n*m,des,map1[i][j]);
                    if(i!=1)addedge(tmp,tmp-m+n*m,S);
                    if(i!=n)addedge(tmp,tmp+m+n*m,S);
                    if(j!=1)addedge(tmp,tmp-1+n*m,S);
                    if(j!=m)addedge(tmp,tmp+1+n*m,S);
                }
            }
        }
        printf("Case %d: %d\n",++cases,sum-Dinic());
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/jackge/p/3239762.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值