传送门:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1976

此题真是神来一割。。。

首先很容易看出它是一个二分图的模型,但连边实在很恼火,感觉无论怎么连都剪不断理还乱的,于是终于膜拜了题解。

这道题关键在于转化,何不让所有点都加上它的最大值,然后选择一个方案令它减去的最少呢,于是我们想到了最小割。

具体讲解可以点这里。我只想总结几点:

1.最小割往往是解决这类二分图问题的神器,但它求的是最小方案,有时需要转化一下

2.在有些已经确定的点上,我们可以连容量为INF的边来保证它不被割掉

3.从源点到汇点的每一条边都代表一种方案,要仔细分析

 

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 64010, maxm = 1000010, maxk = 45;
int fir[maxn], edge[maxm], next[maxm], cap[maxm];
int d[maxn], vd[maxn];
int dd[6][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {-1, 0, 0}, {0, -1, 0}, {0, 0, -1}};
int num[maxk][maxk][maxk];
int vis[maxk][maxk][maxk];
int n, ans, s, t, cnt, tot;
void addedge(int a, int b, int f) {
    edge[cnt] = b; next[cnt] = fir[a]; fir[a] = cnt; cap[cnt ++] = f;
    edge[cnt] = a; next[cnt] = fir[b]; fir[b] = cnt; cap[cnt ++] = 0;
    return;
}
bool check(int x, int y, int z) {
    if(x < 1 || y < 1 || z < 1 || x > n || y > n || z > n) return false;
    return true;
}
int Isap(int r, int aflow) {
    if(r == t) return aflow;
    int flow = aflow, f, md = t - 1;
    for(int i = fir[r]; ~i ; i = next[i]) {
        int v = edge[i];
        if(cap[i]) {
            if(d[r] == d[v] + 1) {
                f = min(flow, cap[i]);
                f = Isap(v, f);
                cap[i] -= f;
                cap[i ^ 1] += f;
                flow -= f;
                if(d[s] >= t) return aflow - flow;
                if(!flow) break;
            }
            md = min(md, d[v]);
        }
    }
    if(flow == aflow) {
        vd[d[r]] --;
        if(!vd[d[r]]) d[s] = t;
        d[r] = md + 1;
        vd[d[r]] ++;
    }
    return aflow - flow;
}
int calc(char c) {
    if(c == '?') return 0;
    if(c == 'P') return 1;
    if(c == 'N') return 2;
}
int main() {
    memset(fir, -1, sizeof(fir));
    scanf("%d", &n);
    s = n * n * n + 1; t = s + 1;
    char c;
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= n; j ++) {
            for(int k = 1; k <= n; k ++) {
                c = getchar();
                while(c != '?' && c != 'P' && c != 'N') c = getchar();
                vis[i][j][k] = calc(c);
                num[i][j][k] = ++tot;
            }
        }
    }
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= n; j ++) {
            for(int k = 1; k <= n; k ++) {
                int res = 0;
                if((i + j + k) & 1) {
                    for(int x = 0; x < 6; x ++) {
                        int tx = i + dd[x][0];
                        int ty = j + dd[x][1];
                        int tz = k + dd[x][2];
                        if(check(tx, ty, tz)) {
                            addedge(num[i][j][k], num[tx][ty][tz], 2);
                            res ++;
                        }
                    }
                    addedge(s, num[i][j][k], res);
                    if(vis[i][j][k] == 1) addedge(s, num[i][j][k], INF);
                    else if(vis[i][j][k] == 2) addedge(num[i][j][k], t, INF);
                } else {
                    for(int x = 0; x < 6; x ++) {
                        int tx = i + dd[x][0];
                        int ty = j + dd[x][1];
                        int tz = k + dd[x][2];
                        if(check(tx, ty, tz)) {
                            res ++;
                        }
                    }
                    addedge(num[i][j][k], t, res);
                    if(vis[i][j][k] == 2) addedge(s, num[i][j][k], INF);
                    else if(vis[i][j][k] == 1) addedge(num[i][j][k], t, INF);
                }
                ans += res;
            }
        }
    }
    vd[0] = t;
    while(d[s] < t) ans -= Isap(s, INF);
    printf("%d\n", ans);
    return 0;
}