[bzoj5010] [FJOI2017]矩阵填数
Description
给定一个 \(h∗w\) 的矩阵,矩阵的行编号从上到下依次为 \(1..h\),列编号从左到右依次\(1..w\)。
在这个矩阵中你需要在每个格子中填入 1..m 中的某个数。
给这个矩阵填数的时候有一些限制,给定 n 个该矩阵的子矩阵,以及该子矩阵的最大值 v,要求你所填的方案满足该子矩阵的最大值为 v。
现在,你的任务是求出有多少种填数的方案满足 n 个限制。
两种方案是不一样的当且仅当两个方案至少存在一个格子上有不同的数。由于答案可能很大,你只需要输出答案 mod 1,000,000,007.
Input
输入数据的第一行为一个数 TT,表示数据组数。
对于每组数据,第一行为四个数 h,w,m,n。
接下来 nn 行,每一行描述一个子矩阵的最大值 v。每行为五个整数 x1,y1,x2,y2,v,表示一个左上角为(x1,y1),右下角为(x2,y2)的子矩阵的最大值为 v。 \(1 \le x1 \le x2 \le h, 1 \le y1 \le y2 \le w\)
Output
对于每组数据输出一行,表示填数方案 mod 1,000,000,0071,000,000,007 后的值。
Sample Input
2
3 3 2 2
1 1 2 2 2
2 2 3 3 1
4 4 4 4
1 1 2 3 3
2 3 4 4 2
2 1 4 3 2
1 2 3 4 4
Sample Output
28
76475
Solution
神仙计数题。
古话说的好,看到计数想容斥。
显然对于矩阵的每一个点的取值有一个范围\([1,mx]\),那么对于\(mx\)相同的点分开计数,由于互不干扰,所以乘法原理乘起来就行了。
然后对于\(mx\)相同的点的集合,可以发现它是由一个或多个限制为\(mx\)的矩形组成的。
那么这一块的答案就是随便选\(-\)选不到\(mx\)的方案。
后面一块可以容斥出来,即只有一个块选不到\(-\)两个块选不到\(+\)三个块选不到\(\cdots\)
然后中间要用到矩形并集的面积,可以先预处理出交集,然后容斥出并集。
总复杂度大概是\(O(3^n)\)。
代码中间有一个挺有用的库函数:\(\_\_builtin\_popcount(x)\),作用是求\(x\)二进制下\(1\)的个数。
然后(好像地球人都知道)枚举子集可以\(for(int~~sta=s;sta;sta=(sta-1)\&s)\)。
细节挺多的。。(调了我一个多小时)
#include<bits/stdc++.h>
using namespace std;
#define int long long
void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
void print(int x) {
if(x<0) putchar('-'),x=-x;
if(!x) return ;print(x/10),putchar(x%10+'0');
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}
const int maxn = 1e4+1;
const int mod = 1e9+7;
int n,m,h,w,col[maxn],row[maxn];
struct square{
int urow,drow,lcol,rcol,mx;
bool operator < (const square &rhs) const {return mx<rhs.mx;}
int calc() {return (rcol-lcol+1)*(drow-urow+1);}
void operator &= (const square &rhs) {
urow=max(urow,rhs.urow),drow=min(drow,rhs.drow);
lcol=max(lcol,rhs.lcol),rcol=min(rcol,rhs.rcol);
}
void init() {read(urow),read(lcol),read(drow),read(rcol),read(mx);}
int check() {return drow<urow||rcol<lcol;}
}a[maxn];
int qpow(int A,int x) {
int res=1;
for(;x;x>>=1,A=1ll*A*A%mod) if(x&1) res=1ll*res*A%mod;
return res;
}
int uni[maxn],inter[maxn];
void solve() {
memset(uni,0,sizeof uni);
memset(inter,0,sizeof inter);
read(h),read(w),read(m),read(n);
for(int i=1;i<=n;i++) a[i].init();
sort(a+1,a+n+1);
for(int s=1;s<(1<<n);s++) {
square tmp=(square){1,h,1,w,0};int bo=0;
for(int i=1;i<=n;i++)
if((s>>(i-1))&1) {tmp&=a[i];if(tmp.check()) {bo=1;break;}}
if(!bo) inter[s]=tmp.calc();
else inter[s]=0;
}
for(int s=1;s<(1<<n);s++) {
for(int sta=s;sta;sta=(sta-1)&s)
uni[s]=uni[s]+inter[sta]*(__builtin_popcount(sta)&1?1:-1);
}
int now=0,all=0,ans=1;
for(int i=1;i<=n;i++) {
now|=(1<<(i-1));if(a[i].mx==a[i+1].mx) continue;
int del=uni[all|now]-uni[all];int res=del,tmp=qpow(a[i].mx,res);
for(int s=now;s;s=(s-1)&now) {
del=uni[all|s]-uni[all];
int tmp2=(qpow(a[i].mx-1,del)*qpow(a[i].mx,res-del))%mod;
tmp=(tmp+tmp2*(__builtin_popcount(s)&1?-1:1))%mod;
}ans=ans*tmp%mod;all|=now,now=0;
}
write((1ll*ans*qpow(m,h*w-uni[(1<<n)-1])%mod+mod)%mod);
}
signed main() {
int t;read(t);
while(t--) solve();
return 0;
}