这题BZOJ上的翻译非常令人恶心>..<0人AC一定是因为没人看得懂题,又懒得去找原网站(雾。
其实题意是这样的:
N行M列的一个矩形(1<=N<=5 1<=M<=149),只能沿着单位正方形的边切,切完之后断成若干个连通块,使得连通块内部没有”不导致断开的无效的切痕”,即同一连通块内的两个格子之间没有切痕。(我依旧说的云里雾里)
我一开始的想法是,在矩形中间的(N-1)*(M-1)个格点中,不存在一个度数正好等于1的格点。我还一直以为是对的,直到我画出了这张图….
12346789处于同一个联通块,而1和2之间有切痕,于是不合题意,而格点1245,2356,4578,5689的度数分别是3,2,2,2,便成就了反例,在N=M=3时我的结果比标准答案大8(你知道是哪8个!)
怎么办呢?
从题意入手!一列一列加,状压DP就好啦!
咦?状压DP?压什么?
压一只并查集!
dp[i][S]表示一个N行i列的矩形,S表示此时(1, i), (2, i), (3, i), .. (N, i)这N个格子的连通情况,两个格子之间可能有:
0:未连通,之后也不能被[i+1,M]列的新格子打通; 1:已经连通;2:未连通,但可能被之后的格子打通,也可以不。
在转移的时候,只要记住:存在公共切痕的两个连通块不能被打通,否则会在联通块内部出现切痕。
这样是一个N*(N-1)/2位的三进制数,会MLE。
我们会发现,(j, i)和(j + 1, i) (1<=j< N)这两个格子是不会有2的情况的,如果没有连通,一定有公共边(就在他们中间嘛),所以是0。如果连通了,就是1。
那么就变成了一个
2N−1∗3(N−1)(N−2)/2
的状态
唯恐天下不乱的出题人,取个模怎么了,害我写高精度
下面的代码,不考虑多组数据,在N=5时,为了预处理转移会TLE,然后我就打了个表
#include <cstdio>
int method[11664][512], N, M;
int origin[5][5], f[10], adj[10][10]; // 0:neither now nor future 1:connected 2:not yet but can be
long long dp[11664], DP[11664];
int POWER(int a, int b)
{
int r = 1;
for (; b; b >>= 1)
{
if (b & 1)
r = r * a;
a = a * a;
}
return r;
}
int F(int x)
{
return f[x] == x ? x : f[x] = F(f[x]);
}
void add(int u, int v)
{
u = F(u), v = F(v);
if (u != v)
f[u] = v;
}
int MakeMethod(int x, int y)
{
int yy = y, yyy = y;
for (int i = 0; i < N + N; i++)
f[i] = i;
for (int i = 0; i < N - 1; i++)
{
origin[i][i + 1] = x & 1, x >>= 1;
if (origin[i][i + 1])
add(i, i + 1);
}
for (int i = 0; i < N - 2; i++)
for (int j = i + 2; j < N; j++)
{
origin[i][j] = x % 3, x /= 3;
if (origin[i][j] == 1)
add(i, j);
}
bool edge_hori[5], edge_vert[4];
for (int i = 0; i < N; i++)
{
edge_hori[i] = (y >> i) & 1;
if (edge_hori[i])
add(i, i + N);
}
for (int i = 0; i < N - 1; i++)
{
edge_vert[i] = (y >> i + N) & 1;
if (edge_vert[i])
add(i + N, i + N + 1);
}
for (int i = 0; i < N; i++)
if (!edge_hori[i] && F(i) == F(i + N))
return -1;
for (int i = 0; i < N - 1; i++)
if (!edge_vert[i] && F(i + N) == F(i + N + 1))
return -1;
for (int i = 0; i < N - 1; i++)
for (int j = i + 1; j < N; j++)
if (origin[i][j] == 0 && F(i) == F(j))
return -1;
for (int i = 0; i < N + N; i++)
for (int j = 0; j < N + N; j++)
adj[i][j] = 0;
for (int i = 0; i < N - 2; i++)
for (int j = i + 2; j < N; j++)
if (origin[i][j] == 0 && F(i) != F(j))
adj[F(i)][F(j)] = adj[F(j)][F(i)] = 1;
for (int i = 0; i < N; i++)
if (F(i) != F(i + N))
adj[F(i)][F(i + N)] = adj[F(i + N)][F(i)] = 1;
for (int i = 0; i < N - 1; i++)
if (F(i + N) != F(i + N + 1))
adj[F(i + N)][F(i + N + 1)] = adj[F(i + N + 1)][F(i + N)] = 1;
int SUM = 0, BAS = 1;
for (int i = 0; i < N - 1; i++)
{
if (F(i + N) == F(i + N + 1))
SUM += BAS;
BAS <<= 1;
}
for (int i = 0; i < N - 2; i++)
for (int j = i + 2; j < N; j++)
{
if (F(i + N) == F(j + N))
SUM += BAS;
else
if (!adj[F(i + N)][F(j + N)])
SUM += BAS << 1;
BAS *= 3;
}
return SUM;
}
int main()
{
-scanf("%d%d", &N, &M);
int XX = POWER(2, N - 1) * POWER(3, (N - 1) * (N - 2) / 2), YY = POWER(2, N + N - 1);
for (int i = 0; i < XX; i++)
for (int j = 0; j < YY; j++)
method[i][j] = MakeMethod(i, j);
for (int i = 0; i < YY; i += (1ll << N))
if (~method[0][i])
dp[method[0][i]]++;
for (int i = 1; i < M; i++)
{
for (int j = 0; j < XX; j++)
DP[j] = 0;
for (int j = 0; j < XX; j++)
if (dp[j])
for (int k = 0; k < YY; k++)
if (~method[j][k])
DP[method[j][k]] += dp[j];
for (int j = 0; j < XX; j++)
dp[j] = DP[j];
}
long long OUT = 0;
for (int i = 0; i < XX; i++)
OUT += dp[i];
printf("%lld\n", OUT);
return 0;
}