FJOI2017 矩阵填数
给定一个 \(h\times w\) 的矩阵,每个格子中将填入 \(1\) 到 \(m\) 中的某个整数。
一个合法的填数方案须满足 \(n\) 条限制,每条限制形如“以 \((x_1,y_1)\) 为左上角,\((x_2,y_2)\) 为右下角的子矩阵中,最大值必须为 \(v\)”。
求填数方案数,对大质数取模。
把这 \(n\) 条限制按照 \(v\) 从小到大排序。
这样,就可以对每个 \(x\) 求出最小限制为 \(x\) 的区域的答案,最后处理没被限制的区域(这显然是 \(m^{S_R}\),\(S_R\) 没被限制的区域的面积)。
答案就是把这些东西全部乘起来。
接下来考虑对于每个 \(x\) 求出最小限制为 \(x\) 的区域的答案。
设 \(A\) 为所有 \(v\) 值等于 \(x\) 的限制构成的集合。我们要满足 \(A\) 中所有限制,并不好算,考虑容斥。
改为对 \(A\) 的每个子集 \(B\),求强制让其不满足的方案数,大概是 \((x-1)^{S_B}x^{S_{A-B}}\) ,其中 \(S_A\) 为集合 \(A\) 中所有子矩阵的并的面积去掉其中有更小限制的面积。然后加加减减就行。
发现我们还要求区域面积,当然可以离散化坐标值然后随便搞,但那个细节巨多。考虑更好写的方法:继续容斥。
我们发现本题中所有的面积都可以转为矩形的交和并。
发现矩形交是好求的,而并的就等于其子集的交加加减减,然后就求完了。枚举子集的子集是 \(O(3^n)\) 的。
每组数据的复杂度为 \(O(3^n+2^n\log(hw))\),\(\log\) 来自快速幂。(当然可以 FMT 把 \(3^n\) 优化掉,优化到 \(O(2^nn+2^n\log(hw))\),但没有必要)。
#include<cstdio>
#include<algorithm>
typedef long long ll;
const int N=10,M=1e9+7;
inline int Pow(int a,int m){int s=1;for(;m;m>>=1)m&1?s=(ll)s*a%M:0,a=(ll)a*a%M;return s;}
int x[N],y[N],xx[N],yy[N],mx[N],t[N],n,m,h,w,cnt[1<<N],ans,tmp;ll cup[1<<N],cap[1<<N];
bool Cmp(const int&i,const int&j){return mx[i]<mx[j];}
int main(){
int a,b,aa,bb,U;
for(int I=0;I<(1<<N);I++)cnt[I]=cnt[I>>1]+(I&1);
int T;scanf("%d",&T);for(;T--;){
scanf("%d%d%d%d",&h,&w,&m,&n);
for(int i=0;i<n;i++)scanf("%d%d%d%d%d",x+i,y+i,xx+i,yy+i,mx+i),t[i]=i;
std::sort(t,t+n,Cmp);
for(int I=0;I<(1<<n);I++){
a=b=0,aa=h,bb=w;
for(int i=0;i<n;i++)if(I&1<<i){
a=std::max(a,x[i]);
b=std::max(b,y[i]);
aa=std::min(aa,xx[i]);
bb=std::min(bb,yy[i]);
}
cap[I]=a>aa||b>bb?0:(ll)(aa-a+1)*(bb-b+1);
}
for(int I=0;I<(1<<n);I++){
cup[I]=0;
for(int J=I;J;J=I&J-1)
cup[I]=(cup[I]+cap[J]*(cnt[J]&1?1:-1));
}
ans=Pow(m,h*w-cup[(1<<n)-1]);
U=0;
for(int l=0,r=0,I;r<n;r++)if(r+1==n||mx[t[r]]!=mx[t[r+1]]){
I=0;
for(int i=l;i<=r;i++)I|=1<<t[i];
tmp=Pow(mx[t[r]],cup[I|U]-cup[U]);
for(int J=I;J;J=I&J-1)
tmp=(tmp+(ll)Pow(mx[t[r]]-1,cup[J|U]-cup[U])*Pow(mx[t[r]],cup[I|U]-cup[J|U])%M*(cnt[J]&1?-1:1)+M)%M;
ans=(ll)ans*tmp%M;
U|=I,l=r+1;
}
printf("%d\n",ans);
}return 0;
}