BZOJ3894文理分科题解
题目
题解
这道题是一道非常经典的网络流最小割题目, 难点主要就在建图上。
首先考虑如何将选择文科和选择理科表示在图上:
这样, 当我们求图的最小割时只可能是剩下以下四种边的集合:
\(\{(文科, 1), (文科, 2)\}\)
\(\{(文科, 1), (2, 理科)\}\)
\(\{(1, 理科), (文科, 2)\}\)
\(\{(1, 理科), (2, 理科)\}\)
可以发现剩下的刚好是一种 \(1\) 与 \(2\) 的选科方式, 只要将所有边的流量减去最小割的流量就可以求出当只有一二条件时每个人选科的最大满意值。
这种建图同样适用于更多的点。
现在, 我们考虑如何将三四条件表示在图上:
我们现将问题扩大一下, 变成如果几个人的选科相同, 则会由他们共同选的哪一科来决定加上多少的贡献。
根据刚才用减去割的思路来思考, 我们只需要考虑如何才能达到当某几个人没有选择同时某一科时, 便将这几个人同时选择这一科时的增加的贡献给减去。
这时候, 我们又要引入一个概念, 就是无限边:
这种边的作用就是联系两个点, 但又不会成为割, 对总流量产生影响。
有了这种边以后, 我们就可以在文科一遍建虚点, 并且文科连到这个虚点的流量是几个点同时选择文科时会产生的贡献, 再将这个虚点像它所表示的几个点连无限边。
理科一边也同样如此操作。
最后就可以建出这样的一张图:
如果几个点中有人断掉的是与文科的边, 那么就需要断掉文科与同时选文之间的边。
这样就可以将整道题转换成最小割。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f3f3f3f3f
#define MAXN 100
class ISAP {
private:
static const int maxn = 100000;
static const int maxm = 5000000;
private:
struct node {
int ed;
int lo;
node *next;
}*s[maxn + 5];
node sp[maxm + 5];
long long sv[maxm + 5];
int sd[maxn + 5];
int sumd[maxn + 5];
int tot;
int H, T;
int len;
private:
void push_edge (int x, int y, long long z) {
node *p = &sp[tot++];
p->ed = y;
p->next = s[x];
p->lo = tot - 1;
sv[tot - 1] = z;
s[x] = p;
}
long long dfs (int now, long long cap) {
if (now == T) {
return cap;
}
long long num = 0;
int mind = len - 1;
for (node *i = s[now]; i; i = i->next) {
if (sv[i->lo]) {
if (sd[now] == sd[i->ed] + 1) {
long long val = dfs (i->ed, min (cap - num, sv[i->lo]));
num += val;
sv[i->lo] -= val;
sv[i->lo ^ 1] += val;
if (sd[H] == len) {
return num;
}
if (num == cap) {
break;
}
}
mind = min (mind, sd[i->ed]);
}
}
if (!num) {
sumd[sd[now]] --;
if (!sumd[sd[now]]) {
sd[H] = len;
}
sd[now] = mind + 1;
sumd[sd[now]] ++;
}
return num;
}
public:
void push (int x, int y, long long z) {
push_edge (x, y, z);
push_edge (y, x, 0);
}
void inite (int n) {
tot = 0;
for (int i = 1; i <= n; i ++) {
s[i] = 0;
}
}
long long maximum_flow (int h, int t, int n) {
long long ans = 0;
H = h;
T = t;
len = n;
for (int i = 1; i <= n; i ++) {
sd[i] = 0;
sumd[i] = 0;
}
sumd[0] = n;
while (sd[H] < n) {
ans += dfs (H, INF);
}
return ans;
}
}isap;
long long sa[MAXN + 5][MAXN + 5];
long long sb[MAXN + 5][MAXN + 5];
long long sc[MAXN + 5][MAXN + 5];
int dis[4 + 5][2] = {{}, {1, 0}, {0, 1}, {-1, 0}, {0, -1}};
void read (int &x) {
x = 0;
char c = getchar ();
bool bl = 0;
while (c < '0' || c > '9') {
if (c == '-') {
bl = 1;
}
c = getchar ();
}
while (c >= '0' && c <= '9') {
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar ();
}
x = bl ? -x : x;
}
void read (long long &x) {
x = 0;
char c = getchar ();
bool bl = 0;
while (c < '0' || c > '9') {
if (c == '-') {
bl = 1;
}
c = getchar ();
}
while (c >= '0' && c <= '9') {
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar ();
}
x = bl ? -x : x;
}
int main () {
isap.inite (0);
int n, m;
long long ans = 0;
read (n), read (m);
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
if ((i + j) & 1) {
read (sa[i][j]);
}
else {
read (sb[i][j]);
}
}
}
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
if ((i + j) & 1) {
read (sb[i][j]);
}
else {
read (sa[i][j]);
}
}
}
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
read (sc[i][j]);
}
}
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
ans += sa[i][j];
ans += sb[i][j];
isap.push (n * m + 1, (i - 1) * m + j, sa[i][j]);
isap.push ((i - 1) * m + j, n * m + 2, sb[i][j]);
for (int k = 1; k <= 4; k ++) {
int x = i + dis[k][0], y = j + dis[k][1];
if (x <= 0 || x > n || y <= 0 || y > m) {
continue;
}
ans += sc[i][j];
isap.push ((i - 1) * m + j, (x - 1) * m + y, sc[i][j]);
isap.push ((x - 1) * m + y, (i - 1) * m + j, sc[i][j]);
}
}
}
printf ("%lld", ans - isap.maximum_flow (n * m + 1, n * m + 2, n * m + 2));
}