题目
思路一
感谢@shadowice1984 的博客提供本思路。
基本思想是这样的:发现 v v v 更小的矩形可以 “覆盖” 掉 v v v 更大的矩形。就是说,这种点只在乎 v v v 最小的限制。
所以我们将点按照它身上的 ( min v ) (\min v) (minv) 分类,每一类都是独立的,可以使用乘法原理。
对于每一类(包含很多矩形),使用容斥原理进行计算:随便选的方案数,减去一个矩形不满足,加上两个矩形不满足……
每一类的大小,应该是并集相减。因为我们其中一部分已经被 v v v 更小的盖住了。也就是 ⋃ i = 1 k S i − ⋃ i = 1 k − 1 S i \bigcup_{i=1}^{k}S_i-\bigcup_{i=1}^{k-1}S_i ⋃i=1kSi−⋃i=1k−1Si ,这里 S i S_i Si 表示 ( min v ) = i (\min v)=i (minv)=i 的点集。
求并集,还是使用容斥(因为并集就是经典的容斥原理啊)。用交集进行容斥。交集可以直接计算。
x x x 个数字,最大值为 v v v ,方案数是 v x − ( v − 1 ) x v^x-(v-1)^x vx−(v−1)x 。总复杂度 O ( 3 n ) \mathcal O(3^n) O(3n) 。
代码一
小知识: __builtin_popcount(x) \text{\_\_builtin\_popcount(x)} __builtin_popcount(x) 返回 32 32 32 位整数 x x x 二进制中 1 1 1 的个数。据说是 O ( 1 ) \mathcal O(1) O(1) 的?
常数大而已。
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){
int a = 0, f = 1; char c = getchar();
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -1;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline int qkpow(long long base,long long q,int Mod){
long long ans = 1; base %= Mod;
for(; q; q>>=1,base=base*base%Mod)
if(q&1) ans = ans*base%Mod;
return ans;
}
const int MaxN = 10, Mod = 1e9+7;
int h, w, m, n;
struct Matrix{
int lx, rx, ly, ry, val;
void input(){
lx = readint(), ly = readint();
rx = readint(), ry = readint();
val = readint();
}
static Matrix wholeMatrix(){ // 整个矩形
Matrix t; t.lx = t.ly = 1;
t.rx = h, t.ry = w; return t;
}
bool operator < (const Matrix &that)const{
return val < that.val;
}
Matrix operator &= (const Matrix &that){ // 求并
lx = max(lx,that.lx), rx = min(rx,that.rx);
ly = max(ly,that.ly), ry = min(ry,that.ry);
return *this;
}
long long size(){ // 避免负数
lx = min(lx,rx+1), ly = min(ly,ry+1);
return (rx-lx+1)*(ry-ly+1);
}
} mat[MaxN];
void input(){
h = readint(), w = readint(), m = readint(), n = readint();
for(int i=0; i<n; ++i) mat[i].input();
sort(mat,mat+n);
}
long long sameSet[1<<MaxN], unionSet[1<<MaxN];
# define sgn(x) ((__builtin_popcount(x)&1)?1:-1) // 容斥
void solve(){
for(int i=1; i<(1<<n); ++i){
Matrix ppl = Matrix::wholeMatrix();
for(int j=0; j<n; ++j)
if(i>>j&1) ppl &= mat[j];
sameSet[i] = ppl.size();
// 求交集
}
for(int i=1; i<(1<<n); ++i){
unionSet[i] = 0;
for(int j=i; j; j=(j-1)&i)
unionSet[i] += sgn(j)*sameSet[j];
// 求并集
}
int nowS = 0, lastS = 0;
long long ans = qkpow(m,1ll*h*w-unionSet[(1<<n)-1],Mod);
// 没有限制的部分,先计算了再说
for(int i=0; i<n; ++i){
nowS |= (1<<i);
if(i+1 < n and mat[i].val == mat[i+1].val)
continue; // 要一次性考虑完所有val相等的
long long all = unionSet[nowS|lastS]-unionSet[lastS];
// 当前val的势力范围(总的面积)
long long ret = qkpow(mat[i].val,all,Mod);
// 当前类别的方案数
for(int j=nowS; j; j=(j-1)&nowS){ // 容斥:有哪些矩形没有被满足
# define cnt (unionSet[j|lastS]-unionSet[lastS])
long long tmp = qkpow(mat[i].val-1,cnt,Mod);
tmp = tmp*qkpow(mat[i].val,all-cnt,Mod)%Mod;
ret = (ret+Mod-sgn(j)*tmp)%Mod;
}
# undef cnt // cnt就是那些没有被满足的矩形的总面积
ans = ans*ret%Mod; // 乘法原理
lastS |= nowS, nowS = 0;
}
printf("%lld\n",ans);
}
int main(){
for(int T=readint(); T; --T)
input(), solve();
return 0;
}
思路二
感谢知名博主@C20203030 的博客的思路:动态规划 + + + 离散化。
矩阵很少,将其离散化,使得每一个小矩形中的点, ( min v ) (\min v) (minv) 的值相等。可以处理他们取到 ( min v ) (\min v) (minv) 会让哪些矩形的条件被满足。
然后用状压 d p \tt{dp} dp 暴力记录有哪些矩形已经被满足。似乎很快?只有 O ( n 2 2 n ) \mathcal O(n^2 2^n) O(n22n) 。
代码二
这种写法不会了。似乎并不好写?

本文探讨了矩形覆盖问题的两种算法解决思路。思路一采用分类与容斥原理,通过将点按(minv)分类,独立计算每类矩形方案数。思路二则结合动态规划与离散化,对矩阵进行离散化处理,记录已满足的矩形状态,实现高效计算。
810

被折叠的 条评论
为什么被折叠?



