[BZOJ1883]Delicious Cake

这题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。
那么就变成了一个 2N13(N1)(N2)/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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值