[FJOI2017]矩阵填数

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

题目

传送门 to luogu

思路一

感谢@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=1kSii=1k1Si ,这里 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(v1)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)

代码二

这种写法不会了。似乎并不好写?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值