HLG 2163 方格取數 (最大網絡流)

題目鏈接:  點擊打開鏈接


Description :

給你一個n*n的格子的棋盤,每個格子裏面有一個非負數。現在從中取出若干個數,使得任意的兩個數所在的格子沒有公共邊,就是說所取的數所在的2個格子不能相鄰,並且取出的數的和最大。

Input :

包括多個測試實例,每個測試實例包括一個整數n 和n*n個非負數x(n<=20, 0 <= x <= 1000)。

Output :

對於每個測試實例,輸出可能取得的最大的和。

Sample Input :

258 83 905 

874 941 662 

733 415 890

Sample Output :

3727 


解析:

一開始的方法(代碼):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define MAXN 25
#define RST(N)memset(N, 0, sizeof(N))
using namespace std;

int n, res, Max;
int Map[MAXN][MAXN], vis[MAXN][MAXN];
const int dx[] = {-1, 1, 1, -1};
const int dy[] = {1, 1, -1, -1};

int max(int x, int y) { return x>y ? x:y; }

bool check(int x, int y)
{
    return x>=1&&x<=n&&y>=1&&y<=n&&!vis[x][y];
}

void solve(int px, int py)
{
    int xx, yy;
    vis[px][py] = 1;
    //printf("%d(px), %d(py)\n", px, py);
    for(int i=0; i<4; i++) {
        xx = px+dx[i];
        yy = py+dy[i];
        if(check(xx, yy)) {
            //printf("%d(xx), %d(yy) is usable\n", xx, yy);
            solve(xx, yy);
        }
        //else printf("%d(xx), %d(yy) is XXXXXXX\n", xx, yy);
    }
    res += Map[px][py];
    //printf("res = %d\n", res);
}

int main()
{
    while(~scanf("%d", &n)) {
        Max = -1;
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=n; j++) {
                scanf("%d", &Map[i][j]);
            }
        }
        if(n == 1) printf("%d\n", Map[1][1]);
        else if(n == 2) {
            printf("%d\n", max(Map[1][1]+Map[2][2], Map[1][2]+Map[2][1]));
        }else {
            for(int i=1; i<=n; i++) {
                for(int j=1; j<=n; j++) {
                    res = 0;
                    RST(vis);
                    solve(i, j);
                    if(res > Max) Max = res;
                }
            }
            printf("%d\n", Max);
        }
    }
    return 0;
}

一開始想到的方法感覺很對很對,然後過了幾天重新做的時候突然想到一個過不去的情況,所以就想到了網絡流;

一開始用的是深搜,思路是:


遍歷二維數組中的每一個點,然後遞歸遍歷每一個和當前點無重邊的點,這種方法能測試到大多數情況;但還是菜鳥思想,因爲好久沒怎麼刷題了,思維有點跟不上了;這種方法對於以下這種情況就測試不對了:

如果輸入數據:

3

520   10    45

10     70    600

10     60    55

按上述方法的話會出現以下兩種情況:


最後出現兩種結果: 520+70+45+55+10 = 700     10+600+60+10=680

這兩種答案都是錯誤的,因爲正確的應該是:  520 + 600 + 60 = 1180



這個題由於數據範圍較大,所以狀態壓縮過不去,需要用網絡流,我重複一遍建圖:

我們知道對於普通二分圖來說,最大獨立點集 + 最小點覆蓋集 = 總點數,類似的,對於有權的二分圖來說,有:

最大點權獨立集 + 最小點權覆蓋集 = 總點權和,

這個題很明顯是要求 最大點權獨立集 ,現在 總點權 已知,我們只要求出來 最小點權覆蓋集 就好了,我們可以這樣建圖,

1,對矩陣中的點進行黑白着色(相鄰的點顏色不同),從源點向黑色的點連一條邊,權值爲該黑色點的權值,

2,從白色的點向匯點連一條邊,權值爲該白色點的權值,

3,然後,對於每一對相鄰的黑白點,從黑點向白點連一條邊,權值爲無窮大。

最後求最小割(最大流),即爲最小點權覆蓋集。

因爲我們求出的最小割集一定是從那些相鄰的黑白點之間的邊(也就是不能用的邊,因爲相鄰的數不能同時選)中選出來的,且是最小代價,也就是說從方格中拿掉的數之和儘量小,那麼剩下的數之和一定是最大的。


代碼如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cstdlib>
#include <algorithm>
#define VM 2520
#define EM 500050
#define INF 0x3f3f3f3f
#define RST(N)memset(N, 0, sizeof(N))
using namespace std;

struct Edge
{
    int u, v, nxt;
    int flow;
}edge[EM << 1];

int n, m, cnt, head[VM];
int src, des, dep[VM];

void addedge(int cu, int cv, int cf)
{
    edge[cnt].u = cu, edge[cnt].v = cv, edge[cnt].flow = cf;
    edge[cnt].nxt = head[cu], head[cu] = cnt++;

    edge[cnt].u = cv, edge[cnt].v = cu, edge[cnt].flow = 0;
    edge[cnt].nxt = head[cv], head[cv] = cnt++;
}

int dir[4][2]= {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

int legal(int i, int j, int k)
{
    int x = i + dir[k][0];
    int y = j + dir[k][1];
    return x>=1 && x<=n && y>=1 && y<=m;
}

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].v;
            if(edge[i].flow>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].v;
         if(edge[i].flow>0 && dep[v]==dep[u]+1 && (tmp=DFS(v, min(minx, edge[i].flow)))) {
             edge[i].flow -= tmp;
             edge[i^1].flow += tmp;
             return tmp;
         }
     }
     return 0;
 }
*/

int DFS(int u,int minx)
{
    int ans = 0;
    if(u == des) return minx;
    for(int i=head[u]; i!=-1 && ans<minx; i=edge[i].nxt) {
        int v = edge[i].v;
        if(edge[i].flow>0 && dep[v]==dep[u]+1){
            int tmp = min(edge[i].flow, minx-ans);
            tmp = DFS(v,tmp);
            ans += tmp;
            edge[i].flow -= tmp;
            edge[i^1].flow += tmp;
        }
    }
    if(!ans) dep[u] = -2;
    return ans;
}

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

void Init()
{
    m = n;
    cnt = src = 0;
    des = n * m + 1;
    memset(head, -1, sizeof(head));
}

int main(int argc, char **argv)
{
    while(~scanf("%d", &n)) {
        Init();
        int x, sum = 0;
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                scanf("%d", &x);
                sum += x;
                if((i+j)%2 == 0) {
                    addedge(src, (i-1)*m+j, x);
                    for(int k=0; k<4; k++) {
                        if(legal(i, j, k)) addedge((i-1)*m+j, (i+dir[k][0]-1)*m+(j+dir[k][1]), INF);
                    }
                }else {
                    addedge((i-1)*m+j, des, x);
                    for(int k=0; k<4; k++) {
                        if(legal(i, j, k)) addedge((i+dir[k][0]-1)*m+(j+dir[k][1]), (i-1)*m+j, INF);
                    }
                }
            }
        }
        int maxflow = Dinic();
        printf("%d\n", sum-maxflow);
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章