[FJOI2017] 矩阵填数
标签:容斥
[FJOI2017]矩阵填数
题目描述
给定一个 \(h \times w\) 的矩阵,矩阵的行编号从上到下依次为 \(1 \sim h\),列编号从左到右依次 \(1 \sim w\)。
在这个矩阵中你需要在每个格子中填入 \(1 \sim m\) 中的某个数。
给这个矩阵填数的时候有一些限制,给定 \(n\) 个该矩阵的子矩阵,以及该子矩阵的最大值 \(v\),要求你所填的方案满足该子矩阵的最大值为 \(v\)。
现在,你的任务是求出有多少种填数的方案满足 \(n\) 个限制。
两种方案是不一样的当且仅当两个方案至少存在一个格子上有不同的数。由于答案可能很大,你只需要输出答案对 \(10 ^ 9 + 7\) 取模后的结果。
其中,\(h,w \leqslant 10^4\) , $n \leqslant 10 $ 。
思路点拨
我们观察数据范围可以发现, \(n \leqslant 10\) ,那么这道题目大概率就是爆搜,状压,容斥之类的。
看到限制:这个限制可以转化为一个子矩阵内,所有的数都不可以超过 \(v\) , 并且起码有一个数等于 \(v\) 。这里的第二个约束十分有意思,这个约束不方便处理,但是我们差分一下,就可以变成 子矩阵内的数都小于等于\(v\) 减去 子矩阵内的数都小于\(v\) 。这样差分之后,我们发现处理简单多了。不过,这样还是不可以直接求解,因为不同矩阵之间的值域互相影响,不好处理。所以我们考虑反面,用全部情况减去不满足条件的情况,考虑容斥。对于容斥部分,我们二进制枚举有那血约束条件没有满足,那么这个约束条件就转化为子矩阵内的数都小于 \(v\) 。最终,我们对每一个格子可以取到的值一起乘法原理累乘就可以了。
子矩阵怎么处理?难道要些什么十分恶心的数据结构?K-d tree?数树套树?不对, $n \leqslant 10 $ 而已,我们暴力的去划分矩阵也只不过最多划分出 \(n^2\) 个矩阵。具体来说,我们考虑吧把每一个子矩阵的边都延长,最后看整个矩阵被划分成了那血矩阵即可 。
时空复杂度分析:时间 \(O(n^2+2^n n^3)\) ,空间 \(O(n^2)\) ,这很好计算。
\(code\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=1e4+10;
const int mod=1e9+7;
int T,n,m,k,q;
int posx[MAXN],posy[MAXN],tot1,tot2;
struct node{
int x1,y1,x2,y2;
int w;
}a[MAXN],b[MAXN];
bool check(node A,node B){
if(A.x1<=B.x1&&B.x2<=A.x2&&A.y1<=B.y1&&B.y2<=A.y2) return 1;
return 0;
}
void clear(){
memset(posx,0,sizeof(posx));
memset(posy,0,sizeof(posy));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
}
int qpow(int a,int b){
int ans=1,base=a;
while(b){
if(b&1) ans=(ans*base)%mod;
base=(base*base)%mod;
b>>=1;
}
return ans;
}
signed main(){
T=read();
while(T--){
clear();
n=read(),m=read(),k=read(),q=read();//矩阵长宽,值域上限,限制数量
tot1=tot2=0;
for(int i=1;i<=q;i++){
posx[++tot1]=b[i].x1=read();
posx[tot1]--;
posy[++tot2]=b[i].y1=read();
posy[tot2]--;
posx[++tot1]=b[i].x2=read();
posy[++tot2]=b[i].y2=read();
b[i].w=read();
}
posx[++tot1]=n;
posy[++tot2]=m;
sort(posx+1,posx+tot1+1);
sort(posy+1,posy+tot2+1);
tot1=unique(posx+1,posx+tot1+1)-posx-1;
tot2=unique(posy+1,posy+tot2+1)-posy-1;
int lasx=1,lasy,cnt=0;
for(int i=1;i<=tot1;i++){
if(!posx[i]) continue;
lasy=1;
for(int j=1;j<=tot2;j++){
if(!posy[j]) continue;
++cnt;
a[cnt].x1=lasx,a[cnt].y1=lasy;
a[cnt].x2=posx[i],a[cnt].y2=posy[j];
a[cnt].w=k;
lasy=posy[j]+1;
}
lasx=posx[i]+1;
}
int ans=0;
for(int i=0;i<(1<<q);i++){
int popcount=0;
for(int j=0;j<q;j++)
if(i&(1<<j))
popcount++;
for(int j=1;j<=cnt;j++)
a[j].w=k;
for(int j=1;j<=q;j++){
int opt=0;
if(i&(1<<(j-1))) opt++;
for(int z=1;z<=cnt;z++)
if(check(b[j],a[z]))
a[z].w=min(a[z].w,b[j].w-opt);
}
int sum=1;
for(int j=1;j<=cnt;j++){
int siz=(a[j].x2-a[j].x1+1)*(a[j].y2-a[j].y1+1);
sum=(sum*qpow(a[j].w,siz))%mod;
}
if(popcount&1) ans=(ans-sum+mod)%mod;
else ans=(ans+sum)%mod;
}
cout<<ans<<endl;
}
return 0;
}