[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)\)
然後(好像地球人都知道)列舉子集可以\(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;
}