抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

LOJ 6068

题解

这类棋盘上放棋子的问题,且根据这个数据范围,很容易想到网络流。

棋子的攻击方式有两种,我们先考虑同一行。我们把一行中极大的连续的 . 组成的区间叫做行连通块。显然不同的行连通块之间是互不影响的。对于同一个行连通块,假设当前已经有 \(x\) 个棋子,那么再放入一个棋子后就会对答案产生 \(x\) 的贡献。同理我们可以定义列连通块,也同样具有行连通块的性质。显然,每个 . 唯一地属于一个行连通块和列连通块。

那么就有一个初步的建模思路:建立两排点,左边是所有行连通块,右边是所有列连通块,源点 \(S\) 向行连通块连边,列连通块向汇点 \(T\) 连边,对于每个是 . 的位置,我们把包含它的行连通块向包含它的列连通块连边。

假设放入一个棋子对应 \(S\)\(T\) 的一个单位的流量,那么这条路径上边上的费用之和应该是放入这个棋子对答案产生的贡献。对于一个行连通块 \(i\),假设它的大小为 \(sz_i\),那么应该从 \(S\)\(i\)\(sz_i\) 条边,第 \(j\) 条边的费用是 \(j-1\),流量上界是 \(1\),这样就能保证第 \(j\) 次在这个行连通块放旗子时贡献是 \(j-1\)。列连通块同理。行连通块向列连通块连的边费用为 \(0\),流量上界为 \(1\)

于是问题变成了求流量恰好为 \(k\) 时的最小费用。可以发现,在跑费用流时,每次跑 SPFA 增广流量只会恰好增加 \(1\),于是在跑费用流时从小到大求出每个 \(k\) 的答案即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
int read(){
register int x = 0;
register char f = 1, ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f ^= 1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
return f ? x : -x;
}
struct Graph{
const static int N = 5005, M = 100005, INF = 0x3f3f3f3f;
int S, T, edge, to[M], pr[M], cap[M], cost[M], hd[N];
Graph(){ edge = 0, memset(hd, -1, sizeof hd); }
void addedge(int u, int v, int w, int c){
to[edge] = v, cap[edge] = w, cost[edge] = c, pr[edge] = hd[u], hd[u] = edge++;
to[edge] = u, cap[edge] = 0, cost[edge] = -c, pr[edge] = hd[v], hd[v] = edge++;
}
int h, t, Q[1000005], dis[N], vis[N], mn[N], pre[N];
bool SPFA(){
memset(dis, 0x3f, sizeof dis), memset(vis, 0, sizeof vis);
h = 0, t = 1, Q[t] = S, dis[S] = 0, vis[S] = 1, pre[S] = 0, mn[S] = INF;
while (h < t){
int u = Q[++h]; vis[u] = 0;
for (register int i = hd[u], v; ~i; i = pr[i])
if (cap[i] && dis[u] + cost[i] < dis[v = to[i]]){
dis[v] = dis[u] + cost[i], mn[v] = std :: min(mn[u], cap[i]), pre[v] = i;
if (!vis[v]) Q[++t] = v, vis[v] = 1;
}
}
return dis[T] != INF;
}
std :: vector<int> MinCostMaxFlow(int _S, int _T){
std :: vector<int> res;
res.push_back(0), S = _S, T = _T;
while (SPFA()){
res.push_back(res[res.size() - 1] + dis[T]);
for (register int i = T; i != S; i = to[pre[i] ^ 1])
cap[pre[i]] -= mn[T], cap[pre[i] ^ 1] += mn[T];
}
return res;
}
}G;
int n, q, cnt, cntr, cntc, rbel[55][55], cbel[55][55], sz[5005];
char a[55][55];
std :: vector<int> ans;
int main(){
n = read();
for (register int i = 1; i <= n; ++i) scanf("%s", a[i] + 1);
cntr = 1;
for (register int i = 1; i <= n; ++i){
if (sz[cntr]) ++cntr;
for (register int j = 1; j <= n; ++j)
if (a[i][j] == '.') rbel[i][j] = cntr, ++sz[cntr];
else if (sz[cntr]) ++cntr;
}
cntc = cntr + 1;
for (register int i = 1; i <= n; ++i){
if (sz[cntc]) ++cntc;
for (register int j = 1; j <= n; ++j)
if (a[j][i] == '.') cbel[j][i] = cntc, ++sz[cntc];
else if (sz[cntc]) ++cntc;
}
for (register int i = 1; i <= cntr; ++i)
for (register int j = 0; j < sz[i]; ++j)
G.addedge(0, i, 1, j);
for (register int i = cntr + 1; i <= cntc; ++i)
for (register int j = 0; j < sz[i]; ++j)
G.addedge(i, cntc + 1, 1, j);
for (register int i = 1; i <= n; ++i)
for (register int j = 1; j <= n; ++j)
if (a[i][j] == '.') G.addedge(rbel[i][j], cbel[i][j], 1, 0);
ans = G.MinCostMaxFlow(0, cntc + 1);
q = read();
while (q--) printf("%d\n", ans[read()]);
}

评论