P6517 [CEOI2010 day1] alliances 题解

· · 题解

P6517 [CEOI2010 day1] alliances

算法

题目

一共给出了 4 种限制。

1 种结点:与四连通的 1 个邻居 Union。

2 种结点:与四连通的 2 个邻居 Union。

3 种结点:与四连通的 3 个邻居 Union。

4 种结点:与四连通的 4 个邻居 Union。

但是第 2 种结点还有一个:就是不能同时与上下左右 Union。

最后输出一种可行方案。

思路

看到这种配对的问题,自然要考虑网络流黑白染色思想了。

规定:对于二元组 (i,j),若 i+j奇数,则为黑点;否则,为白点。而且:黑点只向白点连边,但是白点不向黑点连边。

有无解

判断是否有解只需要判断:黑点的权值和是否等于结盟数(也就是最大流)

建图

初始化

源点 s 向所有黑点连边,权值为输入的数据。

所有白点向汇点 t 连边,权值为输入的数据。

联盟关系

分为两部分:

先考虑不包括第 2 种结点的情况(非人类点):

首先,判断黑白点,若是白点,跳过;否则:黑点向白点连边,权值为 1

再考虑包括第 2 种结点的情况:

发现人类比较特殊,含有两条限制:不能同时与上下左右 Union。

那我们只需要把人类拆为 3 个点:第一个为初始点 x,第二个为管理上下方向的结点 x_1,第三个为管理左右的结点 x_2

对于初始点,仍然限流为 2

对于其他两个结点:

若此节点为黑点

从初结点分别向 x_1x_2 连接一条权值为 1 的边。然后 x_1 向这个结点的上下结点连边,权值为 1x_2 向这个结点的左右结点连边,权值为 1

若此节点为白点

x_1x_2 分别向初结点连接一条权值为 1 的边。然后这个结点的上下结点向 x_1 连边,权值为 1;这个结点的左右结点向 x_2 连边,权值为 1

发现黑白点就是反着建图。

建图总结

源点 s 向黑点连边。

白点向汇点 t 连边。

黑点向白点连边。

把第 2 种结点——人类拆了。

最大流

网络流模板。

统计答案

正边没有流或者反边有流则证明这条边连接的两个结点 Union。

提醒

注意普通点 1,3,4 向人类点 2 连边时不是向初始点 x 连边,而向另外两个管辖方向的点连边。(我吃亏了)

网络流反边流量为 0。(我又吃亏了)

答案的图的大小是原图的 3 倍。(我又双吃亏了)

最好将 (1,1) 结点设为黑点,这样就不用特判只有一个点的情况了(不特判 98 分)。因为如果 (1,1) 是白点,黑点贡献(联盟数量)为 0,最大流也是 0,就判断为有解了,显然错误。(我又双叒吃亏了)

编号方式:

对于二元组 $(i,j)$,编号为 $i\times(m-1)+j$。 对于第 $k$ 个人类的管辖方向的点,编号为 $n\times m+2\times k-x\qquad x\in[0,1]$。 统计答案时要**原图结点的坐标**,开个**桶**,下标为二元组对应的编号,内容存相应的二元组就行了。 ~~这是我的编号方式。(这次没吃亏)~~ ## Code ```cpp #include <bits/stdc++.h> using namespace std; #define judge ((i + dx[k] >= 1 && j + dy[k] >= 1 && i + dx[k] <= n && j + dy[k] <= m) && (a[i + dx[k]][j + dy[k]])) //judge 判断是否越界,是否为无人区。 const int inf = 1 << 30; const int N = 70 + 5; const int M = 3 * N * N; const int s = M - 1;//原点 const int t = M - 2;//汇点 namespace IO { struct type//二元组,其实就是pair<int,int> { int x, y; friend bool operator<(const type &A, const type &B){return A.x != B.x ? A.x < B.x : A.y < B.y;} type(int x = 0, int y = 0) : x(x), y(y){} }; template <typename Type> void read(Type &n) { Type w=1;char x=getchar();n=0; while(x<'0'||x>'9'){if(x=='-')w=-1;x=getchar();} while(x>='0'&&x<='9'){n=(n<<1)+(n<<3)+(x^48);x=getchar();} n*=w; } template <typename Type,typename...Etc> void read(Type &n,Etc &...etcs) { read(n);read(etcs...); } template <typename Type> void write(Type x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } } using namespace IO; namespace network//网络流模板,此部分不解释 { struct edge { int u; int v; int w; int nxt; edge(int u = 0, int v = 0, int w = 0, int nxt = 0) : u(u), v(v), w(w), nxt(nxt){} }; edge e[M << 4]; int head[M]; int dep[M]; int cur[M]; int ecnt = 1; void add(int u, int v, int w) { e[++ecnt] = edge(u, v, w, head[u]); head[u] = ecnt; e[++ecnt] = edge(v, u, 0, head[v]); head[v] = ecnt; } bool bfs() { queue<int> q; memcpy(cur, head, sizeof(cur)); memset(dep, 0, sizeof(dep)); dep[s] = 1; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].v; if (e[i].w && !dep[v]) { dep[v] = dep[u] + 1; q.push(v); } } } return dep[t]; } int dfs(int u, int flow) { if (u == t) return flow; int i, rest = flow; for (i = cur[u]; i; i = e[i].nxt) { int v = e[i].v; if (e[i].w && dep[v] == dep[u] + 1) { int k = dfs(v, min(e[i].w, rest)); rest -= k; e[i].w -= k; e[i ^ 1].w += k; if (!rest) break; } } cur[u] = i; return flow - rest; } int maxflow() { int ans = 0; while (bfs()) ans += dfs(s, inf); return ans; } } using namespace network; int n, m, sum; int a[N][N], b[N * 3][N * 3];//原图和答案图,记得开 3 倍 int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0, 0}; //下,上,右,左 int personcnt; //人的个数 map<type, type> person; //人拆出来的两个管辖方向的点 map<type, bool> Union; //判断两个点是否联合。 type point[M]; //桶存相应的点的坐标 bool black(type p)//判断是否为黑点 { return !((p.x + p.y) & 1); } int get_num(type p)//得到二元组的编号 { return (p.x - 1) * m + p.y; } void get_edge()//建边 { for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { read(a[i][j]); type now = type(i, j); sum += a[i][j] * black(now); if (black(now))//s->黑点 add(s, get_num(now), a[i][j]); else//白点->t add(get_num(now), t, a[i][j]); point[get_num(now)] = now; if (a[i][j] == 2) { ++personcnt;//人类+1 person[now] = type(n * m + 2 * personcnt - 1, n * m + 2 * personcnt);//拆点 point[person[now].x] = now; point[person[now].y] = now; if (black(now))//黑点->x1,x2 { add(get_num(now), person[now].x, 1); add(get_num(now), person[now].y, 1); } else//x1,x2->白点 { add(person[now].x, get_num(now), 1); add(person[now].y, get_num(now), 1); } } } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { type u = type(i, j); if (!black(u) || a[i][j] == 0) continue;//白点,无人区不连边 for (int k = 0; k < 4; k++) { type v = type(i + dx[k], j + dy[k]); if (judge) { if (a[i + dx[k]][j + dy[k]] == 2)//连向的点是人类 { if (a[i][j] == 2)//当前点是人类 { if (k < 2)//上下 add(person[u].x, person[v].x, 1); else//左右 add(person[u].y, person[v].y, 1); } else//当前点是普通点 { if (k < 2) add(get_num(u), person[v].x, 1); else add(get_num(u), person[v].y, 1); } } else//连向的点是普通点 { if (a[i][j] == 2) { if (k < 2) add(person[u].x, get_num(v), 1); else add(person[u].y, get_num(v), 1); } else add(get_num(u), get_num(v), 1); } } } } } } void get_map() { for (int i = 1; i <= n * 3; i++) for (int j = 1; j <= m * 3; j++) b[i][j] = '.';//初始化全部没人 for (int i = 2; i <= ecnt; i += 2)//Union { if (e[i].u != s && e[i].v != t && !e[i].w) { int u = get_num(point[e[i].u]); int v = get_num(point[e[i].v]); Union[type(u, v)] = true; Union[type(v, u)] = true; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (a[i][j])//若当前不是无人区 { int xl = 3 * (i - 1) + 1, xr = 3 * i; int yl = 3 * (j - 1) + 1, yr = 3 * j; int xmid = (xl + xr) >> 1, ymid = (yl + yr) >> 1; b[xmid][ymid] = 'O'; for (int k = 0; k < 4; k++) if (judge && Union.count(type(get_num(type(i, j)), get_num(type(i + dx[k], j + dy[k])))))//判断四个方向是否 Union b[xmid + dx[k]][ymid + dy[k]] = 'X'; } } } } signed main() { int total = 0; read(n, m); get_edge();//建边 if (sum != maxflow())//判断 { printf("Impossible!"); return 0; } get_map();//答案 for (int i = 1; i <= n * 3; i++)//print { for (int j = 1; j <= m * 3; j++) { putchar(b[i][j]); } putchar('\n'); } return 0;//never give up. } ``` ## 后言 感谢 @[Ptilopsis_w](https://www.luogu.com.cn/user/239167) DaLao 对本人错误思想的指出及正确思想的提供。 感谢管理员大大抽出时间审核。