HDU 3657 Game (SAP | Dinic | EK 三種算法的比較)

Game

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 737    Accepted Submission(s): 305


php

Problem Description
onmylove has invented a game on n × m grids. There is one positive integer on each grid. Now you can take the numbers from the grids to make your final score as high as possible. The way to get score is like
the following:
● At the beginning, the score is 0;
● If you take a number which equals to x, the score increase x;
● If there appears two neighboring empty grids after you taken the number, then the score should be decreased by 2(x&y). Here x and y are the values used to existed on these two grids. Please pay attention that "neighboring grids" means there exits and only exits one common border between these two grids.

Since onmylove thinks this problem is too easy, he adds one more rule:
● Before you start the game, you are given some positions and the numbers on these positions must be taken away.
Can you help onmylove to calculate: what's the highest score onmylove can get in the game?
 

 

Input
Multiple input cases. For each case, there are three integers n, m, k in a line.
n and m describing the size of the grids is n ×m. k means there are k positions of which you must take their numbers. Then following n lines, each contains m numbers, representing the numbers on the n×m grids.Then k lines follow. Each line contains two integers, representing the row and column of one position
and you must take the number on this position. Also, the rows and columns are counted start from 1.
Limits: 1 ≤ n, m ≤ 50, 0 ≤ k ≤ n × m, the integer in every gird is not more than 1000.
 

 

Output
For each test case, output the highest score on one line.
 

 

Sample Input
2 2 1 2 2 2 2 1 1 2 2 1 2 7 4 1 1 1
 

 

Sample Output
4 9
Hint
As to the second case in Sample Input, onmylove gan get the highest score when calulating like this: 2 + 7 + 4 - 2 × (2&4) - 2 × (2&7) = 13 - 2 × 0 - 2 × 2 = 9.
 

 

Author
onmylove
 

 

Source
 

 

Recommend
lcy
 

 大體題意:ios

    給出一個n*m的矩陣,讓你從中取出必定數量的數字。若是在矩陣中兩兩相鄰的數字被取到的話須要付出必定的代價。並且給出某些點,規定這些點必定須要取到。求最多能夠取到多少點。
 
大體思路:
    怎麼說呢,這道題乍看上去和hdoj 1569:方格取數很類似,也很像是一個二分圖的最大點權獨立集問題。可是問題出的很巧妙,也就沒有辦法往模版上面套了。把矩陣中的點按照橫縱座標之和的奇偶性分紅兩個集合,設超級源匯點,源點第一個集合中的全部點連邊,容量爲這個點表明的數字的值。第二個集合中的全部點向匯點連邊,容量也是這個點的值。第一個集合中點都向他周圍的點連邊,容量爲他們同時被取時的消耗。若是一個點必須取,那就將他和源/匯點的容量設爲inf,保證這條邊不被割掉。用全部點的權值之和sum減去這個圖的最小割獲得的就是答案。總的來講,ac後的感覺就是,這是一道須要意識流的題目
 
SAP():
#include<iostream>
#include<cstdio>
#include<cstring>

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,k,cnt,head[VM],map[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 src,des;

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);

    while(~scanf("%d%d%d",&n,&m,&k)){
        cnt=0;
        memset(head,-1,sizeof(head));

        src=0; des=n*m+1;
        int sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map[i][j]);
                sum+=map[i][j];
            }
        int x,y;
        while(k--){
            scanf("%d%d",&x,&y);
            if((x+y)%2==0)
                addedge(src,(x-1)*m+y,INF);
            else
                addedge((x-1)*m+y,des,INF);
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int tmp=(i-1)*m+j;
                if((i+j)%2==0)
                    addedge(src,tmp,map[i][j]);
                else
                    addedge(tmp,des,map[i][j]);
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if((i+j)%2==0){
                    int tmp=(i-1)*m+j;
                    if(i>1)    addedge(tmp,tmp-m,2*(map[i][j]&map[i-1][j]));
                    if(i<n)    addedge(tmp,tmp+m,2*(map[i][j]&map[i+1][j]));
                    if(j>1)    addedge(tmp,tmp-1,2*(map[i][j]&map[i][j-1]));
                    if(j<m)    addedge(tmp,tmp+1,2*(map[i][j]&map[i][j+1]));
                }
        printf("%d\n",sum-SAP(des+1));
    }
    return 0;
}

 

上面用SAP算法,只用了78ms,而下面的Dinic用了1600ms,Orz。。。。。。。。。算法

Dinic():網絡

#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,k,cnt,head[VM],src,des;
int map[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;
        }
    }
    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);

    while(~scanf("%d%d%d",&n,&m,&k)){
        cnt=0;
        memset(head,-1,sizeof(head));

        src=0; des=n*m+1;
        int sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map[i][j]);
                sum+=map[i][j];
            }
        int x,y;
        while(k--){
            scanf("%d%d",&x,&y);
            if((x+y)%2==0)
                addedge(src,(x-1)*m+y,INF);
            else
                addedge((x-1)*m+y,des,INF);
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int tmp=(i-1)*m+j;
                if((i+j)%2==0)
                    addedge(src,tmp,map[i][j]);
                else
                    addedge(tmp,des,map[i][j]);
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if((i+j)%2==0){
                    int tmp=(i-1)*m+j;
                    if(i>1)    addedge(tmp,tmp-m,2*(map[i][j]&map[i-1][j]));
                    if(i<n)    addedge(tmp,tmp+m,2*(map[i][j]&map[i+1][j]));
                    if(j>1)    addedge(tmp,tmp-1,2*(map[i][j]&map[i][j-1]));
                    if(j<m)    addedge(tmp,tmp+1,2*(map[i][j]&map[i][j+1]));
                }
        printf("%d\n",sum-Dinic());
    }
    return 0;
}

 下面的EK算法直接超時了,暫且不知道是否是還有什麼優化,。。。。。。。app

EK():優化

#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,k,cnt,head[VM],max_flow;   //max_flow是最大流
int map[110][110],flow[110][110];   // map[i][j]是每條邊的容量,flow[i][j]是每條邊的流量
int res[VM],pre[VM];    //res[]是每一個點的剩餘流量,pre[]是每一個點的父親

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 EK(int src,int des){
    max_flow=0;
    queue<int> q;
    while(!q.empty())
        q.pop();
    memset(flow,0,sizeof(flow));    //最開始每條邊的流量都是0
    while(1){
        memset(res,0,sizeof(res));  //殘餘流量得變0,一開始全部點都沒流入對吧
        res[src]=INF;   //源點嘛,剩餘流量無限是必須的...
        q.push(src);    //從源點開始進行BFS找增廣路
        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(!res[v] && edge[i].cap>flow[u][v]){    //該點剩餘流量爲0 且 容量大於流量,也就是找到了新的結點
                    pre[v]=u;   //找到新結點,父節點得記錄一下吧
                    q.push(v);
                    res[v]=min(res[u],edge[i].cap-flow[u][v]);    //若是u的剩餘流量能填滿uv就填滿,不能的話就把u這點的流量所有流向uv
                }
            }
        }
        if(res[des]==0)     //若是當前已是最大流,匯點沒有殘餘流量
            return max_flow;
        for(int u=des;u!=src;u=pre[u]){     //若是還能增廣,那麼回溯,從匯點往回更新每條走過的邊的流量
            flow[pre[u]][u]+=res[des];  //更新正向流量   (注意這裏更新的是流量,而不是容量)
            flow[u][pre[u]]-=res[des];  //更新反向流量
        }
        max_flow+=res[des];
    }
}

int main(){

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

    while(~scanf("%d%d%d",&n,&m,&k)){
        cnt=0;
        memset(head,-1,sizeof(head));

        int src=0, des=n*m+1;
        int sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                scanf("%d",&map[i][j]);
                sum+=map[i][j];
            }
        int x,y;
        while(k--){
            scanf("%d%d",&x,&y);
            if((x+y)%2==0)
                addedge(src,(x-1)*m+y,INF);
            else
                addedge((x-1)*m+y,des,INF);
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int tmp=(i-1)*m+j;
                if((i+j)%2==0)
                    addedge(src,tmp,map[i][j]);
                else
                    addedge(tmp,des,map[i][j]);
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if((i+j)%2==0){
                    int tmp=(i-1)*m+j;
                    if(i>1)    addedge(tmp,tmp-m,2*(map[i][j]&map[i-1][j]));
                    if(i<n)    addedge(tmp,tmp+m,2*(map[i][j]&map[i+1][j]));
                    if(j>1)    addedge(tmp,tmp-1,2*(map[i][j]&map[i][j-1]));
                    if(j<m)    addedge(tmp,tmp+1,2*(map[i][j]&map[i][j+1]));
                }
        printf("%d\n",sum-EK(src,des));
    }
    return 0;
}
相關文章
相關標籤/搜索