每周五篇博客:(4/5)
https://codeforces.com/contest/2098/problem/D
题意
每个机场都有一个行李索赔区,巴尔贝索沃机场也不例外。在某个时候,Sheremetyevo的一位管理员提出了一个不寻常的想法:将行李索赔传送带的传统形状从轮播更改为更复杂的形式。
假设行李索赔区域表示为大小 n × m n \times m n×m 的矩形网格。管理局提出,输送机的路径应通过 p 1 , p 2 , … , p 2 k + 1 p_1, p_2, \ldots, p_{2k+1} p1,p2,…,p2k+1 的单元,其中 p i = ( x i , y i ) p_i = (x_i, y_i) pi=(xi,yi) 。
对于每个单元格 p i p_i pi 和下一个单元格 p i + 1 p_{i+1} pi+1 (其中 1 ≤ i ≤ 2 k 1 \leq i \leq 2k 1≤i≤2k ),这些单元格必须具有共同的侧面。此外,路径必须很简单,这意味着对于没有一对索引 i ≠ j i \neq j i=j ,如果单元格 p i p_i pi 和 p j p_j pj 的单元格。
不幸的是,路线计划被溢出的咖啡意外宠坏了,只保留了带有奇数指数的细胞: p 1 , p 3 , p 5 , … , p 2 k + 1 p_1, p_3, p_5, \ldots, p_{2k+1} p1,p3,p5,…,p2k+1 。您的任务是确定给定这些 k + 1 k+1 k+1 单元格的原始完整路径 p 1 , p 2 , … , p 2 k + 1 p_1, p_2, \ldots, p_{2k+1} p1,p2,…,p2k+1 的方法数量。
由于答案可能很大,因此输出它模拟 1 0 9 + 7 10^9+7 109+7 。
思路
首先对于 p 2 i − 1 , p 2 i + 1 p_{2i - 1},p_{2i + 1} p2i−1,p2i+1 ,如果 ∣ p 2 i − 1 − p 2 i + 1 ∣ ≠ 2 |p_{2i - 1}-p_{2i + 1}| \ne 2 ∣p2i−1−p2i+1∣=2 的话答案一定是 0 0 0 ,即我们无法通过两步的操作从 p 2 i − 1 p_{2i - 1} p2i−1 走到 p 2 i + 1 p_{2i + 1} p2i+1
接下来我们进行建图,如果 p 2 i − 1 p_{2i - 1} p2i−1 与 p 2 i + 1 p_{2i + 1} p2i+1 处于同一行/同一列,那么在他们中间的点连一条自环边;如果 p 2 i − 1 p_{2i - 1} p2i−1 与 p 2 i + 1 p_{2i + 1} p2i+1 不处于同一行/同一列,那么我们在这两个点之间可以通过的两个点进行连边
例如点 ( 1 , 1 ) , ( 1 , 3 ) (1, 1), (1, 3) (1,1),(1,3) ,那么我们会在点 ( 1 , 2 ) (1, 2) (1,2) 处连一条 ( 1 , 2 ) − ( 1 , 2 ) (1, 2) - (1, 2) (1,2)−(1,2) 的无向边
例如点 ( 1 , 1 ) , ( 2 , 2 ) (1, 1), (2, 2) (1,1),(2,2) ,那么我们会在点 ( 1 , 2 ) (1, 2) (1,2) 与 ( 2 , 1 ) (2, 1) (2,1) 处连一条 ( 1 , 2 ) − ( 2 , 1 ) (1, 2) - (2,1) (1,2)−(2,1) 的无向边
连边本质上是我们让可以作为 p 2 i p_{2i} p2i 的点之间连接起来,意思是只要我们选择边的某个端点都是合法的一种方案作为链接 p 2 i − 1 , p 2 i + 1 p_{2i - 1},p_{2i + 1} p2i−1,p2i+1
连边后我们会获得若干个连通块,而这些连通块有以下几种可能:
- 如果该连通块的点数比边数要少,那么答案为 0 0 0 。这是因为我们每链接一条边都代表我们需要去选择一个点去链接某对 p 2 i − 1 , p 2 i + 1 p_{2i - 1},p_{2i + 1} p2i−1,p2i+1 。如果边比点多的话,那么点数根本就不够选择的,所以答案一定是 0 0 0
- 如果该连通块的点数和边数一样,表示该连通块一定存在环,那么同样分成两种情况
- 该环是某个节点的自环。此时答案根据乘法原理乘以 1 1 1 。原因见另一种情况的解释
- 该环包含了超过一个节点的环。此时答案根据乘法原理乘以 2 2 2 。这个情况可以参考题目的样例 2 2 2 。如果我们选择一个点来固定的话,那么其余点的选择也都固定了。而对于 p 2 i − 1 , p 2 i + 1 p_{2i - 1},p_{2i + 1} p2i−1,p2i+1 中的 p 2 i p_{2i} p2i 有两种选择的可能,所以选择的点都有两种方案,那么答案也就是两种了。同理,对于上一种情况,因为有一个 p 2 i p_{2i} p2i 点是唯一选择的,那么答案只能乘以 1 1 1
- 如果该连通块的点数比边数要多,那么该连通块一定是棵树,并且答案根据乘法原理乘以点的数量即可。这是因为我们要选边的数量的点来作为一种可行的方案,所以在这棵树中我们可以选择任意一个点作为无用点后可以得到一个固定的可行方案,而这个无用点的选择方案有点的数量个
事实上我们也不需要实际建边再去跑连通分量,只需要用并查集维护每个点都在哪些连通块即可,具体细节见代码
代码
struct DSU {
int n;
std::vector<int> fa, sz;
std::vector<i64> val;
explicit DSU(int n): n(n) {
fa.assign(n + 1, 0);
sz.assign(n + 1, 1);
val.assign(n + 1, 0);
for (int i = 1; i <= n; i ++) fa[i] = i;
}
int find(int x) {
if (x == fa[x]) return fa[x];
int f = fa[x];
fa[x] = find(fa[x]);
val[x] += val[f];
return fa[x];
}
bool judge(int x, int y) {
int dx = find(x), dy = find(y);
if (dx == dy) return true;
else return false;
}
void merge(int x, int y, i64 w = 0) {
int dx = find(x), dy = find(y);
if (dx != dy) {
fa[dx] = dy;
sz[dy] += sz[dx];
val[dx] = -val[x] + val[y] + w;
}
}
};
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
DSU dsu(n * m);
std::vector<int> cnt(n * m + 1), loop(n * m + 1);
auto get = [&](int x, int y) {return (x - 1) * m + y;};
std::vector<PII> a(k + 1);
for (int i = 0; i <= k; i ++) std::cin >> a[i].first >> a[i].second;
for (int i = 1; i <= k; i ++) {
auto [x1, y1] = a[i - 1];
auto [x2, y2] = a[i];
if (std::abs(x1 - x2) + std::abs(y1 - y2) != 2) {
std::cout << "0\n";
return ;
}
int u, v;
if (std::abs(x1 - x2) == 1) {
u = get(x1, y2), v = get(x2, y1);
} else if (x1 == x2) {
u = get(x1, (y1 + y2) / 2), v = get(x1, (y1 + y2) / 2);
} else if (y1 == y2) {
u = get((x1 + x2) / 2, y1), v = get((x1 + x2) / 2, y1);
}
if (!dsu.judge(u, v)) {
cnt[dsu.find(u)] += cnt[dsu.find(v)];
loop[dsu.find(u)] |= loop[dsu.find(v)];
dsu.merge(v, u);
}
cnt[dsu.find(u)] ++;
if (u == v) loop[dsu.find(u)] = 1;
}
Z ans = 1;
for (int i = 0; i <= n * m; i ++) if (dsu.find(i) == i) {
int sz = dsu.sz[i];
if (sz < cnt[i]) {
ans = 0;
break;
} else if (sz == cnt[i]) {
if (loop[i]) {
ans *= 1;
} else {
ans *= 2;
}
} else {
ans *= sz;
}
}
std::cout << ans << '\n';
}