题解 P2711 【小行星】

Tweetuzki

2018-03-11 19:56:35

Solution

看到数据范围,以及“消除”、“最少次数”的字样,就知道与最小割有关。问题在于怎么割呢? 要消除一颗小行星,可以从三个面来消除。$x$ 面,$y$ 面和 $z$ 面。不难想到,将这三个面上的每一个坐标建两个点——入点和出点。由入点向出点连一条容量为 $1$ 的边。当入点和出点之间的这条边被割掉时,就说明是在这个面的这一个点消除了一次。 我们想要消除所有的小行星,就说明每一颗小行星在 $x$ 面,$y$ 面或 $z$ 面这三个面中,至少有一个面把它消除了。也就是说,不存在一条连通 $x-y-z$ 的路径,这就是割了。 于是我们对于每一颗小行星,从 $x$ 面的出点向 $y$ 面的入点连一条容量为 $INF$ 的边(仅为保证点之间的连通性),从 $y$ 面的出点向 $z$ 面的入点连容量为 $INF$ 的边。再从超级源向每个 $x$ 面的入点连容量为 $INF$ 的边,从每个 $z$ 面的出点向超级汇连容量为 $INF$ 的边。 建出来的图大致如下(样例): ![](https://cdn.luogu.com.cn/upload/pic/15466.png) 根据上面的分析,显然地,就是求 $s-t$ 最小割,这样就能保证每颗小行星都被消在某个面上。 但是……图好乱…… 不难发现,这张图是可以简化的。注意到从超级源 $S$ 向每一个 $x$ 连出的边,下一条边都是从 $x$ 的入点向 $x$ 的出点连的一条容量为 $1$ 的边。根据 [Total Flow](https://www.luogu.org/problemnew/show/P2936) 一题可知,这两条边就类似于串联的关系,是可以合并的。 于是我们简化了这张图,改为直接从超级源 $S$ 向每个 $x$ (入点就可以不要了,于是就没有出入点之分了)连一条权值为 $1$ 的边。从 $z$ 面的边到终点同理,也可以简化。 简化后的图大致如下: ![](https://cdn.luogu.com.cn/upload/pic/15465.png) 这样只要拆 $y$ 面的点就好了。是不是清爽多了? 最后求解该图的最小割即可。 ```cpp #include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; const int MAXN = 500, INF = 0x7F7F7F7F; struct edge { int to, cap, rev; }; int n, k; vector <edge> G[MAXN*4+2]; edge make_edge(int to, int cap, int rev) { edge x; x.to = to, x.cap = cap, x.rev = rev; return x; } void add_edge(int from, int to, int cap) { G[from].push_back( make_edge(to, cap, G[to].size()) ); G[to].push_back( make_edge(from, 0, G[from].size() - 1) ); } void init() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { int x, y, z; scanf("%d %d %d", &x, &y, &z); add_edge(x, y + 500, INF); add_edge(y + 500, y + 1000, 1); add_edge(y + 1000, z + 1500, INF); } for (int i = 1; i <= 500; ++i) { add_edge(0, i, 1); add_edge(i + 1500, 2001, 1); } } namespace Dinic { int level[MAXN*4+2]; int iter[MAXN*4+2]; void bfs(int s) { bool mark[MAXN*4+2]; queue <int> q; memset(mark, 0, sizeof mark); memset(level, -1, sizeof level); q.push(s); mark[s] = true; level[s] = 0; while (!q.empty()) { int u = q.front(); q.pop(); mark[u] = false; for (int i = 0; i < G[u].size(); ++i) { edge &e = G[u][i]; if (e.cap > 0 && level[e.to] < 0) { level[e.to] = level[u] + 1; if (!mark[e.to]) { mark[e.to] = true; q.push(e.to); } } } } } int dfs(int u, int t, int f) { if (u == t) return f; for (int &i = iter[u]; i < G[u].size(); ++i) { edge &e = G[u][i]; if (e.cap > 0 && level[e.to] > level[u]) { int d = dfs(e.to, t, min(f, e.cap)); if (d > 0) { e.cap -= d; G[e.to][e.rev].cap += d; return d; } } } return 0; } int max_flow(int s, int t) { int flow = 0; for (;;) { bfs(s); if (level[t] < 0) return flow; memset(iter, 0, sizeof iter); int f; while ( (f = dfs(s, t, INF)) > 0 ) flow += f; } } } int main() { init(); printf("%d\n", Dinic::max_flow(0, 2001)); return 0; } ```