没有传送门
题目大意
有一个 的网格图,每一个格子有一个权值。对于坐标 ,若 为偶数,那么保证这个地方的权值为 。
你有 个 形状的石头可以放在这个网格图上,每个石头占三个格子。它通过旋转有四种放置方式,仅会使得在拐角处的那个格子的权值变为 。
这个网格图还有 个特殊的位置,石头的任何部位都不能位于这些格子上,但是保证这些位置的权值一定是 (有可能 为奇数的格子也是特殊的)。
求放置至多 个格子(可以不放)后权值和的最小值。
。
考场上的思路
考虑网络流,但是什么都没有考虑出来。主要在一个 形石头需要覆盖两个权值为 的格子上卡住了。这种“一个对两个”的模型不能直接套用网络流。
于是压 行动规,结果爆零啦!我太弱啦!调查表明,错误原因是,我是按 是奇数的情况编码的,却没有注意到 是偶数时情况不同,但是测试点的 全是偶数!
思路
还是网络流,不过这里我们要跳出一对二的思维限制。如果像这样建图,一对二是没法做的:
但是如果我们能够把
和
分成两类,然后这样建图:
就可以处理一对二的情况了,不过前提是能够分类。对于这道题的 形石头而言,我们不妨这样看它:把它看成一条 形的路径,要求起点一定在奇数行上,终点一定在偶数行上。我们把在奇数行的 为偶数的点分成一类,把在偶数行的这种点分成一类,中间是 为奇数的点,这道题就做完啦!!!@#¥%……&*
至于不能放石头的偶数点,只需要不从源点连边或者不向汇点连边。至于不能放石头的奇数点,就不要向四周连边了。
现在要体现覆盖的奇数点总代价最大,怎么做呢?“有流量流过一个点”,我们自然会想到拆点。拆点后,中间连一条容量为 ,费用为权值的边,跑最大费用可行流即可。
如何跑最大费用可行流呢?首先把费用取负改成跑最小费用可行流,然后不断增广,直到费用为正。这里可以加一个结点来限流,所以就不讨论流量超过 的情况了。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef int INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0; bool positive = true;
char ch = getchar();
while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
if (ch == '-') { positive = false; ch = getchar(); }
while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20]; int length = 0;
if (x < 0) putchar('-'); else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
}
const int maxn = 55;
int n, m, k;
int v[maxn][maxn];
bool forbid[maxn][maxn];
#define RunInstance(x) delete new x
struct brute
{
LL f[12][26][1 << 10];
enum { FRT = 1, SEC, TRD, FRTH };
int temp[12];
void MakeTransfer(int x, int S)
{
if (x & 1)
{
int base = n >> 1;
if (temp[0] == base)
{
int nextS = (S << base) & ((1 << n) - 1);
LL sum = 0;
int cnt = 0;
for (int i = 1; i <= temp[0]; i++)
{
if (temp[i])
{
sum += v[x][i << 1];
cnt++;
}
switch (temp[i])
{
case FRT:
{
nextS |= 1 << (base + i);
break;
}
case SEC:
{
nextS |= 1 << (base + i - 1);
break;
}
case TRD:
{
nextS |= 1 << (base + i - 1);
nextS |= 1 << (i - 1);
break;
}
case FRTH:
{
nextS |= 1 << (base + i);
nextS |= 1 << (i - 1);
break;
}
}
}
for (int i = 0; i + cnt <= m; i++)
f[x + 1][i + cnt][nextS] = std::max(f[x + 1][i + cnt][nextS], f[x][i][S] + sum);
return;
}
temp[++temp[0]] = 0;
MakeTransfer(x, S);
temp[0]--;
int i = temp[0] + 1;
int y = i << 1;
if (!forbid[x][y])
{
if (!forbid[x - 1][y] && !(S & (1 << (base + i - 1))) &&
!forbid[x][y + 1] && !(S & (1 << i)))
{
temp[++temp[0]] = FRT;
MakeTransfer(x, S);
temp[0]--;
}
if (!forbid[x - 1][y] && !(S & (1 << (base + i - 1))) &&
!forbid[x][y - 1] && !(S & (1 << (i - 1))) &&
(!temp[0] || temp[temp[0]] != FRT && temp[temp[0]] != FRTH))
{
temp[++temp[0]] = SEC;
MakeTransfer(x, S);
temp[0]--;
}
if (!forbid[x][y - 1] && !(S & (1 << (i - 1))) &&
!forbid[x + 1][y] &&
(!temp[0] || temp[temp[0]] != FRT && temp[temp[0]] != FRTH))
{
temp[++temp[0]] = TRD;
MakeTransfer(x, S);
temp[0]--;
}
if (!forbid[x][y + 1] && !(S & (1 << i)) &&
!forbid[x + 1][y])
{
temp[++temp[0]] = FRTH;
MakeTransfer(x, S);
temp[0]--;
}
}
}
else
{
int base = n - (n >> 1);
if (temp[0] == base)
{
int nextS = (S << base) & ((1 << n) - 1);
LL sum = 0;
int cnt = 0;
for (int i = 1; i <= temp[0]; i++)
{
if (temp[i])
{
sum += v[x][(i << 1) - 1];
cnt++;
}
switch (temp[i])
{
case FRT:
{
nextS |= 1 << (base + i - 1);
break;
}
case SEC:
{
nextS |= 1 << (base + i - 2);
break;
}
case TRD:
{
nextS |= 1 << (base + i - 2);
nextS |= 1 << (i - 1);
break;
}
case FRTH:
{
nextS |= 1 << (base + i - 1);
nextS |= 1 << (i - 1);
break;
}
}
}
for (int i = 0; i + cnt <= m; i++)
f[x + 1][i + cnt][nextS] = std::max(f[x + 1][i + cnt][nextS], f[x][i][S] + sum);
return;
}
temp[++temp[0]] = 0;
MakeTransfer(x, S);
temp[0]--;
int i = temp[0] + 1;
int y = (i << 1) - 1;
if (!forbid[x][y])
{
if (!forbid[x - 1][y] && !(S & (1 << (base + i - 1))) &&
!forbid[x][y + 1] && !(S & (1 << (i - 1))))
{
temp[++temp[0]] = FRT;
MakeTransfer(x, S);
temp[0]--;
}
if (!forbid[x - 1][y] && !(S & (1 << (base + i - 1))) &&
!forbid[x][y - 1] && !(S & (1 << (i - 2))) &&
(!temp[0] || temp[temp[0]] != FRT && temp[temp[0]] != FRTH))
{
temp[++temp[0]] = SEC;
MakeTransfer(x, S);
temp[0]--;
}
if (!forbid[x][y - 1] && !(S & (1 << (i - 2))) &&
!forbid[x + 1][y] &&
(!temp[0] || temp[temp[0]] != FRT && temp[temp[0]] != FRTH))
{
temp[++temp[0]] = TRD;
MakeTransfer(x, S);
temp[0]--;
}
if (!forbid[x][y + 1] && !(S & (1 << (i - 1))) &&
!forbid[x + 1][y])
{
temp[++temp[0]] = FRTH;
MakeTransfer(x, S);
temp[0]--;
}
}
}
}
brute() : f(), temp()
{
int U = 1 << n;
for (int i = 1; i <= n; i++)
for (int S = 0; S < U; S++)
MakeTransfer(i, S);
LL sum = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
sum += v[i][j];
printOut(sum - *std::max_element(f[n + 1][m], f[n + 1][m] + (1 << n)));
}
};
struct work
{
struct NetworkFlow
{
struct Edge
{
int from, to, cap, cost, flow;
Edge() {}
Edge(int from, int to, int cap, int cost) : from(from), to(to), cap(cap), cost(cost), flow() {}
};
int s, t;
std::vector<std::vector<int> > G;
std::vector<Edge> edges;
void addEdge(int from, int to, int cap, int cost)
{
edges.push_back(Edge(from, to, cap, cost));
edges.push_back(Edge(to, from, 0, -cost));
int i = edges.size();
G[from].push_back(i - 2);
G[to].push_back(i - 1);
}
std::vector<LL> dis;
std::vector<int> opt;
std::vector<int> pre;
struct Queue
{
static const int size = maxn * maxn * 10;
int c[size];
int head, tail;
void clear() { head = tail = 0; }
void push(int x) { c[tail] = x; tail = tail + 1 < size ? tail + 1 : 0; }
void pop() { head = head + 1 < size ? head + 1 : 0; }
int front() { return c[head]; }
bool empty() { return head == tail; }
} q;
std::vector<bool> inQ;
bool Bellman_Ford(int& flow, LL& cost)
{
dis.clear();
dis.resize(G.size(), LLONG_MAX / 2);
opt.clear();
opt.resize(G.size());
q.clear();
q.push(s);
dis[s] = 0;
inQ[s] = true;
opt[s] = INT_MAX;
while (!q.empty())
{
int from = q.front();
q.pop();
inQ[from] = false;
for (int i = 0; i < G[from].size(); i++)
{
const Edge& e = edges[G[from][i]];
if (e.flow < e.cap && dis[from] + e.cost < dis[e.to])
{
dis[e.to] = dis[from] + e.cost;
opt[e.to] = std::min(e.cap - e.flow, opt[from]);
pre[e.to] = G[from][i];
if (!inQ[e.to])
{
q.push(e.to);
inQ[e.to] = true;
}
}
}
}
if (!opt[t] || dis[t] >= 0)
return false;
flow += opt[t];
cost += opt[t] * dis[t];
for (int u = t; u != s; u = edges[pre[u]].from)
{
edges[pre[u]].flow += opt[t];
edges[pre[u] ^ 1].flow -= opt[t];
}
return true;
}
LL mincost()
{
int flow = 0;
LL cost = 0;
inQ.clear();
inQ.resize(G.size());
pre.clear();
pre.resize(G.size());
while (Bellman_Ford(flow, cost));
return cost;
}
} nf;
int N;
int idx[maxn][maxn][2];
work() : N(), idx()
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
idx[i][j][0] = ++N;
if ((i + j) & 1)
idx[i][j][1] = ++N;
}
nf.G.resize(N + 3);
nf.s = N + 1;
nf.t = N + 2;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!forbid[i][j])
{
if ((i + j) & 1)
{
if (i & 1)
{
if (i > 1) nf.addEdge(idx[i][j][1], idx[i - 1][j][0], 1, 0);
if (i < n) nf.addEdge(idx[i][j][1], idx[i + 1][j][0], 1, 0);
if (j > 1) nf.addEdge(idx[i][j - 1][0], idx[i][j][0], 1, 0);
if (j < n) nf.addEdge(idx[i][j + 1][0], idx[i][j][0], 1, 0);
}
else
{
if (i > 1) nf.addEdge(idx[i - 1][j][0], idx[i][j][0], 1, 0);
if (i < n) nf.addEdge(idx[i + 1][j][0], idx[i][j][0], 1, 0);
if (j > 1) nf.addEdge(idx[i][j][1], idx[i][j - 1][0], 1, 0);
if (j < n) nf.addEdge(idx[i][j][1], idx[i][j + 1][0], 1, 0);
}
nf.addEdge(idx[i][j][0], idx[i][j][1], 1, -v[i][j]);
}
else
{
if (i & 1)
nf.addEdge(nf.s, idx[i][j][0], 1, 0);
else
nf.addEdge(idx[i][j][0], nf.t, 1, 0);
}
}
nf.s = 0;
nf.addEdge(nf.s, N + 1, m, 0);
LL sum = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
sum += v[i][j];
printOut(sum + nf.mincost());
}
};
void run()
{
n = readIn();
m = readIn();
k = readIn();
m = std::min(m, n * n / 4);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
v[i][j] = readIn();
for (int i = 1; i <= k; i++)
{
int x = readIn();
int y = readIn();
forbid[x][y] = true;
}
for (int i = 0; i <= n; i++)
forbid[0][i] = forbid[i][0] =
forbid[n + 1][i] = forbid[i][n + 1] = true;
if (n <= 10 && !(n & 1)) // 编码方式只适用于 n 为偶数的情况……
RunInstance(brute);
else
RunInstance(work);
}
int main()
{
#ifndef LOCAL
freopen("marshland.in", "r", stdin);
freopen("marshland.out", "w", stdout);
#endif
run();
return 0;
}
总结
一个新的模型:一对二与分类。