[URAL1627] Join
Solution
算法标签🏷️:轮廓线 DP
为了保证连通,考虑维护每个节点所在的连通块编号,每次考虑到 \((x,y)\) 位置时,分讨向上和向左两条边的存在情况,并更新每个点所在的连通块编号即可。注意到,可以强制钦定每个点的连通块编号小于等于当前点的位置,这样不难得到状态数是 \(O(m!)\)。每次更新每个点所在的连通块编号的复杂度是 \(O(m)\) 的,综上总复杂度为 \(O(nm^2(m!))\)。
以上为核心思路,下文讲述是细节维护,默认标号从 \(0\) 开始。将每个点连通块编号组成的序列压成一个数采用特殊进制即可,第 \(0\) 个位置的上限是 \(0\),第 \(1\) 个位置的上限是 \(1\),……,第 \(m-1\) 个位置的上限是 \(m-1\)。那么,对于序列 \((a_0,a_1,\dots,a_{m-1})\) 来讲,其对应着的值为
这样,解决了状态存储的问题。接下来,考虑 \((i,j)\) 位置的转移(令 \(\text{mask}\) 表示状态):
-
条件:\(g_{i-1,j}\ne \texttt{*},g_{i,j - 1}\ne \texttt{*},g_{i,j}\ne \texttt{*}\) 且 \(\text{mask}_j\ne \text{mask}_{j-1}\)(解释:第一个条件由题意可得,第二个条件是保证不会连出环)。
状态变化:为了满足连通块编号小于等于点的位置的条件,需分讨 \(\text{mask}_j\) 和 \(\text{mask}_{j-1}\) 的大小关系:- \(\text{mask}_j>\text{mask}_{j-1}\):则将所有连通块编号为 \(\text{mask}_j\) 的位置,连通块编号均改为 \(\text{mask}_{j-1}\)。
- \(\text{mask}_j<\text{mask}_{j-1}\):则将所有连通块编号为 \(\text{mask}_{j-1}\) 的位置,连通块编号均改为 \(\text{mask}_{j}\)。
-
条件:\(g_{i-1,j}\ne \texttt{*},g_{i,j}\ne \texttt{*}\) 且 \(\text{mask}_j\) 连通块不只有 \(j\) 这一个位置(解释:第一个条件由题意可得,第二个条件是保证连通性,若只有 \(j\) 这一个位置,那么不连接向上的边会导致 \(\text{mask}_j\) 与其余点不可能连通)。
状态变化:由于第 \(j\) 个位置的连通块编号发生改变,所以需要将所有连通块编号为 \(j\) 的位置更改连通块编号。
-
条件:\(g_{i,j-1}\ne \texttt{*},g_{i,j}\ne \texttt{*}\)
状态变化:不变,因为 \(j\) 位置的连通块编号未发生改变。
-
条件:\(\text{mask}_j\) 连通块不只有 \(j\) 这一个位置(解释:保证连通性,若只有 \(j\) 这一个位置,那么不连接向上的边会导致 \(\text{mask}_j\) 与其余点不可能连通)
状态变化:由于第 \(j\) 个位置的连通块编号发生改变,所以需要将所有连通块编号为 \(j\) 的位置更改连通块编号。
终止状态:若最后一行存在 \(\texttt{*}\),则 \(\texttt{*}\) 位置的连通块编号是其本身,\(\texttt{.}\) 位置的连通块编号均为第一个 \(\texttt{.}\) 位置。
注意⚠️:若最后一行全为 \(\texttt{*}\),则直接删除最后一行,不能保留。
技巧:状态改变可以使用结构体,这样会方便很多。
Code
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
struct DS {
int ds[9], pw[9];
DS(int v) {
pw[0] = 1;
for (int i = 1; i < 9; i ++)
pw[i] = pw[i - 1] * i;
for (int i = 0; i < 9; i ++)
ds[i] = v / pw[i] % (i + 1);
}
int& operator[] (int x) { return ds[x]; }
int HASH() {
int res = 0;
for (int i = 0; i < 9; i ++)
res += ds[i] * pw[i];
return res;
}
};
int n, m;
char gr[9][9];
int f[362880], g[362880];
int main() {
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
cin >> n >> m;
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++)
cin >> gr[i][j];
while (1) {
int ok = 1;
for (int i = 0; i < m; i ++)
ok &= gr[n - 1][i] == '*';
if (!ok) break;
n --;
}
const int mod = 1e9;
int mul = 1, st = 0, ed = 0, ok = -1;
for (int i = 0; i < m; i ++) {
if (gr[n - 1][i] == '*') ed += i * mul;
else {
if (ok == -1) ok = i;
ed += ok * mul;
}
st += i * mul, mul *= (i + 1);
}
g[st] = 1;
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++) {
memset(f, 0, sizeof f);
for (int k = 0; k < mul; k ++) {
if (!g[k]) continue;
DS temp(k), back(k);
// condition 1
int cur = -1;
if (i && gr[i - 1][j] == '.' && temp[j] == j) {
for (int t = j + 1; t < m; t ++)
if (temp[t] == temp[j]) {
if (cur == -1) cur = temp[t] = t;
else temp[t] = cur;
}
} else cur = 0;
temp[j] = j;
if (~cur) (f[temp.HASH()] += g[k]) %= mod;
if (gr[i][j] == '*') continue;
temp = back;
// condition 2
if (i && gr[i - 1][j] == '.') (f[k] += g[k]) %= mod;
// condition 3
cur = -1;
if (i && gr[i - 1][j] == '.' && temp[j] == j) {
for (int t = j + 1; t < m; t ++)
if (temp[t] == temp[j]) {
if (cur == -1) cur = temp[t] = t;
else temp[t] = cur;
}
} else cur = 0;
if (~cur && j && gr[i][j - 1] == '.') {
temp[j] = temp[j - 1];
(f[temp.HASH()] += g[k]) %= mod;
}
temp = back;
// condition 4
if (temp[j] != temp[j - 1] && i && j &&
gr[i][j - 1] == '.' && gr[i - 1][j] == '.') {
int u = temp[j], v = temp[j - 1];
if (u < v) swap(u, v);
for (int t = 0; t < m; t ++)
if (temp[t] == u) temp[t] = v;
(f[temp.HASH()] += g[k]) %= mod;
}
}
swap(f, g);
}
cout << g[ed] << endl;
return 0;
}
Debug
【输入】
4 4
****
*..*
*..*
****
【输出】
4

浙公网安备 33010602011771号