第46届ICPC东亚洲区域赛(昆明)B Blocks题解

本文介绍了一种解决二维平面上染色问题的动态规划方法,通过将二维坐标转换为一维,利用状态压缩和位运算进行优化。文章详细阐述了如何将大矩形划分为小格子,并通过位移操作将二维状态转化为一维。最后,通过递归和逆向动态规划计算期望的染色步数。
摘要由CSDN通过智能技术生成
  • 特此声明:该文章中的代码和解法均来自博主cup-pyy,我只是对其代码添加了详细注解,以及部分思路的详细化,原文链接见:这里

二维平面转为一维

注意我们最多含有22个坐标,但是实际上只会有21*21个格子
如何判断二维平面中的格子数量全部染黑?一维情况状态压缩十分容易,二维的情况将若干个一维拼接即可

        bitset<500> t;
        //二维平面转换为一维
        //虽然22个坐标
        // 21 * 21个格子看看是否都能染满
        lop(i, 0, n)
        {
            r[i].a.x = lower_bound(xs + 1, xs + cntx + 1, r[i].a.x) - xs;
            r[i].a.y = lower_bound(ys + 1, ys + cnty + 1, r[i].a.y) - ys;
            r[i].b.x = lower_bound(xs + 1, xs + cntx + 1, r[i].b.x) - xs;
            r[i].b.y = lower_bound(ys + 1, ys + cnty + 1, r[i].b.y) - ys;
            mask[i].reset();//
            t.reset();
            //标记一个格子被染黑,表示这个格子的左下角被染黑
            t = (1 << (r[i].b.y - r[i].a.y)) - 1;
            //[r[i].a.y, r[i].b.y - 1]全部为1
            //转换为下标为0开始
            //[r[i].a.y - 1, r[i].b.y - 2]全部都是1
            t <<= r[i].a.y - 1;
            //对于区间[r[i].a.y - 1, ]
            //r[i].b.y - r[i].a.y = len
            //(1 << len) - 1先初始先赋值[0, len - 1]全是1
            //然后区间同时加上,为[r[i].a.y - 1, r[i].a.y - 1 + len - 1]

            //大矩形一行的状态为[0, cnty - 1]
            //所以往上移动一行为 << (1 * (cnty - 1))
            lop(j, r[i].a.x - 1, r[i].b.x - 1)
            {
                //区间[r[i].a.x, b[i].b.x - 1]
                //0-index为[r[i].a.x - 1, b[i].b.x - 2]
                mask[i] |= (t << (j * (cnty - 1)));
            }
        }

期望dp方程化简

cnt为状态i1的数量

  • f [ k ] = 1 + 1 n ∑ i = 0 n f [ k ∣ ( 1 < < i ) ] f[k]=1+\cfrac{1}{n}\sum_{i=0}^{n}f[k\mid(1<<i)] f[k]=1+n1i=0nf[k(1<<i)] 1 1 1表示固定的染色步数花费
  • f [ k ] = 1 + c n t n f [ k ] + 1 n ∑ ( k & ( 1 < < i ) ) = = 0 f [ k ∣ ( 1 < < i ) ] f[k]=1+\cfrac{cnt}{n}f[k]+\cfrac{1}{n}\sum_{(k\&(1<<i))==0}f[k|(1<<i)] f[k]=1+ncntf[k]+n1(k&(1<<i))==0f[k(1<<i)]
  • n − c n t n f [ k ] = 1 + 1 n ∑ ( k & ( 1 < < i ) ) = = 0 f [ k ∣ ( 1 < < i ) ] \cfrac{n-cnt}{n}f[k]=1+\cfrac{1}{n}\sum_{(k\&(1<<i))==0}f[k|(1<<i)] nncntf[k]=1+n1(k&(1<<i))==0f[k(1<<i)]
  • f [ k ] = ( 1 + 1 n ∑ ( k & ( 1 < < i ) ) = = 0 f [ k ∣ ( 1 < < i ) ] ) × n n − c n t f[k]=(1+\cfrac{1}{n}\sum_{(k\&(1<<i))==0}f[k|(1<<i)])\times\cfrac{n}{n-cnt} f[k]=(1+n1(k&(1<<i))==0f[k(1<<i)])×ncntn

完整的注释代码

#include <bits/stdc++.h>
using namespace std;
#define el '\n'
#define pb push_back
#define x first
#define y second
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define lop(i, a, b) for (int i = (a); i < (b); i++)
#define dwn(i, a, b) for (int i = (a); i >= (b); i--)

typedef long long LL;
typedef pair<int, int> PII;

struct read
{
    static const int M = 1 << 21;
    char buf[M], *S = buf, *P = buf, c, f;
    inline char getc()
    {
        return (S == P && (P = (S = buf) + fread(buf, 1, 1 << 21, stdin), S == P) ? EOF : *S++);
    }
    template <typename T>
    read &operator>>(T &x)
    {
        for (c = 0; !isdigit(c); c = getc())
            f = c;
        for (x = 0; isdigit(c); c = getc())
            x = x * 10 + (c - '0');
        return x = (f ^ '-') ? x : -x, *this;
    }
} fin;

constexpr int N = 23, M = 2e6 + 10, B = 10, md = 998244353;
const double PI = acos(-1), eps = 1e-8;

int T, n, m;
int w, h, cntx, cnty;
int f[1 << B];
int xs[N], ys[N];

int fpow(int a, int b)
{
    LL res = 1;
    while (b)
    {
        if (b & 1)
            res = res * a % md;
        b >>= 1;
        a = 1LL * a * a % md;
    }
    return res;
}

struct Rect
{
    PII a, b;
} r[15];

int inv[N];
int k;
bitset<500> mask[10], state;
//mask表示覆盖的二维格子的一维形式

void dfs(int u, int s)
{
    if(u == n)
    {//0-idx,边界
        f[s] = (state.count() == k) ? 0 : - 1;
        //计算1的数量是否为k,即是否全部染黑了
        return;
    }
    bitset<500> bk = state;//递归保留现场
    //不取第u个
    dfs(u + 1, s);
    state = bk;//还原
    state |= mask[u];
    //取第u个
    dfs(u + 1, s | (1 << u));
    return ;
}

int main()
{
    rep(i, 1, 19)
    {
        inv[i] = fpow(i, md - 2);
    }
    fin >> T;
    while (T--)
    {
        fin >> n >> w >> h;
        cntx = cnty = 0;
        lop(i, 0, n)
        {
            fin >> r[i].a.x >> r[i].a.y >> r[i].b.x >> r[i].b.y;
            if (r[i].a.x > w)
                r[i].a.x = w;
            if (r[i].a.y > h)
                r[i].a.y = h;
            if (r[i].b.x > w)
                r[i].b.x = w;
            if (r[i].b.y > h)
                r[i].b.y = h;
            xs[++cntx] = r[i].a.x;
            xs[++cntx] = r[i].b.x;
            ys[++cnty] = r[i].a.y;
            ys[++cnty] = r[i].b.y;
        }
        sort(xs + 1, xs + cntx + 1);
        sort(ys + 1, ys + cnty + 1);
        cntx = unique(xs + 1, xs + cntx + 1) - (xs + 1);
        cnty = unique(ys + 1, ys + cnty + 1) - (ys + 1);
        k = (cntx - 1) * (cnty - 1);
        bitset<500> t;
        //二维平面转换为一维
        //虽然22个坐标
        // 21 * 21个格子看看是否都能染满
        lop(i, 0, n)
        {
            r[i].a.x = lower_bound(xs + 1, xs + cntx + 1, r[i].a.x) - xs;
            r[i].a.y = lower_bound(ys + 1, ys + cnty + 1, r[i].a.y) - ys;
            r[i].b.x = lower_bound(xs + 1, xs + cntx + 1, r[i].b.x) - xs;
            r[i].b.y = lower_bound(ys + 1, ys + cnty + 1, r[i].b.y) - ys;
            mask[i].reset();//
            t.reset();
            //标记一个格子被染黑,表示这个格子的左下角被染黑
            t = (1 << (r[i].b.y - r[i].a.y)) - 1;
            //[r[i].a.y, r[i].b.y - 1]全部为1
            //转换为下标为0开始
            //[r[i].a.y - 1, r[i].b.y - 2]全部都是1
            t <<= r[i].a.y - 1;
            //对于区间[r[i].a.y - 1, ]
            //r[i].b.y - r[i].a.y = len
            //(1 << len) - 1先初始先赋值[0, len - 1]全是1
            //然后区间同时加上,为[r[i].a.y - 1, r[i].a.y - 1 + len - 1]

            //大矩形一行的状态为[0, cnty - 1]
            //所以往上移动一行为 << (1 * (cnty - 1))
            lop(j, r[i].a.x - 1, r[i].b.x - 1)
            {
                //区间[r[i].a.x, b[i].b.x - 1]
                //0-index为[r[i].a.x - 1, b[i].b.x - 2]
                mask[i] |= (t << (j * (cnty - 1)));
            }
        }
        state.reset();
        dfs(0, 0);
        if(f[(1 << n) - 1] == -1)
        {
            puts("-1");
            continue;
        }
        //进行逆推期望dp过程
        dwn(i, (1 << n) - 1, 0)
        {
            if(f[i] != -1)
                continue;//f[i] == -1 是终点,期望步数为0
            int cnt = 0, res = 0;
            lop(j, 0, n)
            {
                if(i >> j & 1)
                    cnt ++; //有 cnt / n种情况会转移到自身
                else 
                    res = (res + 1LL * inv[n] * f[i | (1 << j)]) % md;
            }
            f[i] = (1LL + res) * n % md * inv[n - cnt] % md;
        }
        cout << f[0] << el;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值