XCPCXCPCdp容斥Kunming ICPC 2025 B.Blocks
amiracle
题意:
平面上有 n 个方块,
给出左上右下角坐标, (x1, y1), (x2, y2)
不断重复以下操作
- 随机选任意一个 (可能重复选同一个)
- 将该方块涂黑
一直操作直到 [0, 0] × [W, H]
的区域被完全涂黑
求操作期望次数
(n ≤ 10, x, y, W, H ≤ 1e9)
解析:
n 很小, 容易想到状压 dp
定义 dpS →
从状态 S 到完全染黑的期望次数,S 中的 1 代表该矩形选过
期望 dp 方程化简
若染色已经完成, 则期望步数为 0, dpS = 0
选到已选过的矩形, 概率 , 形成环,
移项即可解决
令 S 为当前的状态, x 为
S 中 1 的个数
判断已经全部染黑
提供两种方法
1. 二维离散+二维差分:
不难发现大部分区域都是无用的, 对 x, y 轴分别离散即可
离散后大概只有 21 行/列
现在想要 [x1, y1] × [x2, y2]
的矩形区域 +1
离散后范围较小, 可以直接暴力对矩形区域 +1
之后暴力判断是否全 !0 即可
这里用二维差分进行优化
二维差分原理:
首先我们知道对 diff(x, y)
+1 的效果是 (x, y)
右下角区域都 +1
所有就有此方法:
- (x1, y1)
+1 让右下角都 +1
- (x2 + 1, y1)
-1, 这样就删掉了一部分多的
- (x1, y2 + 1)
-1, 也删掉了一部分多的
- (x2 + 1, y2 + 1)
+1 修正即可, 补上多减的 1
也就是对 4 个点进行修改
1
| diff[x1][y1]++, diff[x2+1][y2+1]++, diff[x2+1][y1]--, diff[x1][y2+1]--;
|
最后二维前缀和即可
总时间复杂度 O(szx ⋅ szy + n)
一个简单的反例是:

这种情况 [0, 0] × [5, 5]
所有整数点都覆盖了,但整个区域明显没有完全覆盖
解决办法:
我们可以把 (x, y)
当作格子的坐标
区域就变成了 [ (x1, y1), (x2, y2) )
(也就是 (x1, y1)到 (x2 − 1, y2 − 1)
的所有格子)
即 (x2, y2)
被当作一个开点
然后完全覆盖就等价于 mat 目标范围全 !0
2. 容斥算面积:
(怎么还有这种方法)
容斥公式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| int full = (1 << n) - 1; vector<ll> cap(1 << n); for(int mask=1; mask<=full; mask++){ int u = -inf, d = inf, l = -inf, r = inf; for(int p=0; p<n; p++){ if(!(mask >> p & 1)) continue; auto [x1, y1, x2, y2] = pt[p]; u = max(u, x1); d = min(d, x2); l = max(l, y1); r = min(r, y2); } cap[mask] = max(0ll, 1ll * (d - u + 1) * (r - l + 1)); }
vector<ll> cup(1 << n); for(int mask=1; mask<=full; mask++){ for(int S=mask; S; S=mask&(S-1)){ int x = __builtin_popcount(S); cup[mask] += 1ll * (x&1? 1:-1) * cap[S]; } }
|
MYCODE
离散 (181ms)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int mo = 998244353; const int MX = 10; vector<ll> inv(MX+1); void init(){ inv[1] = 1; for(int i=2; i<=MX; i++){ inv[i] = (mo - mo / i) * inv[mo % i] % mo; } }
void solve(){ int n, W, H; cin >> n >> W >> H; vector<tuple<int, int, int, int>> pt(n); vector<int> xs{0, W}, ys{0, H}; for(auto& [x1, y1, x2, y2] : pt){ cin >> x1 >> y1 >> x2 >> y2; xs.insert(xs.end(), {x1, x2}); ys.insert(ys.end(), {y1, y2}); }
sort(xs.begin(), xs.end()); xs.erase(unique(xs.begin(), xs.end()), xs.end()); sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end()); for(auto& [x1, y1, x2, y2] : pt){ x1 = lower_bound(xs.begin(), xs.end(), x1) - xs.begin(); x2 = lower_bound(xs.begin(), xs.end(), x2) - xs.begin(); y1 = lower_bound(ys.begin(), ys.end(), y1) - ys.begin(); y2 = lower_bound(ys.begin(), ys.end(), y2) - ys.begin(); } W = lower_bound(xs.begin(), xs.end(), W) - xs.begin(); H = lower_bound(ys.begin(), ys.end(), H) - ys.begin();
int full = (1 << n) - 1; vector<bool> valid(1 << n); for(int mask=1; mask<=full; mask++){ vector<vector<int>> mat(xs.size()+1, vector<int>(ys.size()+1)); for(int p=0; p<n; p++){ if(mask >> p & 1){ auto [x1, y1, x2, y2] = pt[p]; mat[x1][y1]++; mat[x2][y1]--; mat[x1][y2]--; mat[x2][y2]++; } } for(int i=0; i<=xs.size(); i++){ for(int j=0; j<=ys.size(); j++){ if(i) mat[i][j] += mat[i-1][j]; if(j) mat[i][j] += mat[i][j-1]; if(i&&j) mat[i][j] -= mat[i-1][j-1]; } }
bool ok = true; for(int i=0; i<W; i++){ for(int j=0; j<H; j++){ ok &= (mat[i][j]!=0); } } if(ok) valid[mask] = true; }
if(!valid[full]){ cout << -1 << '\n'; return; }
vector<ll> dp(1 << n); for(int mask=full; mask>=0; mask--){ if(valid[mask]) continue; for(int p=0; p<n; p++){ int S = mask | (1 << p); if(!(mask >> p & 1)){ dp[mask] += 1ll * dp[S] * inv[n] % mo; dp[mask] %= mo; } } int cnt = __builtin_popcount(mask); dp[mask] = 1ll * (dp[mask] + 1) * n * inv[n-cnt] % mo; dp[mask] %= mo; } cout << dp[0] << '\n'; }
int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); init();
int t = 1; cin >> t; while(t--) solve();
return 0; }
|
容斥 (54ms)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int mo = 998244353; const int inf = 0x3f3f3f3f;
const int MX = 10; vector<ll> inv(MX+1); void init(){ inv[1] = 1; for(int i=2; i<=MX; i++){ inv[i] = (mo - mo / i) * inv[mo % i] % mo; } }
void solve(){ int n, W, H; cin >> n >> W >> H; vector<tuple<int, int, int, int>> pt(n); for(auto& [x1, y1, x2, y2] : pt){ cin >> x1 >> y1 >> x2 >> y2; }
int full = (1 << n) - 1; vector<ll> cap(1 << n); for(int mask=1; mask<=full; mask++){ int u = 0, d = W, l = 0, r = H; for(int p=0; p<n; p++){ if(!(mask >> p & 1)) continue; auto [x1, y1, x2, y2] = pt[p]; u = max(u, x1); d = min(d, x2); l = max(l, y1); r = min(r, y2); } cap[mask] = 1ll * max(0, d-u) * max(0, r-l); }
vector<ll> cup(1 << n); for(int mask=1; mask<=full; mask++){ for(int S=mask; S; S=mask&(S-1)){ int x = __builtin_popcount(S); cup[mask] += 1ll * (x&1? 1:-1) * cap[S]; } }
vector<bool> valid(1 << n); for(int mask=1; mask<=full; mask++){ valid[mask] = (cup[mask]==1ll*W*H); }
if(!valid[full]){ cout << -1 << '\n'; return; }
vector<ll> dp(1 << n); for(int mask=full; mask>=0; mask--){ if(valid[mask]) continue; for(int p=0; p<n; p++){ int S = mask | (1 << p); if(!(mask >> p & 1)){ dp[mask] += 1ll * dp[S] * inv[n] % mo; dp[mask] %= mo; } } int cnt = __builtin_popcount(mask); dp[mask] = 1ll * (dp[mask] + 1) * n * inv[n-cnt] % mo; dp[mask] %= mo; } cout << dp[0] << '\n'; }
int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); init();
int t = 1; cin >> t; while(t--) solve();
return 0; }
|
vp 时思路假了, 以为只跟个数有关, 因此推了个假的状态转移式 QAQ
©2025 By AMIRACLE