Description
让我们来考虑一个单位立方体建成的模型。这个建筑的底是一个n m的单位正方形网格。在每个正方形上面,堆着若干个(可能是0)个单位立方体。每个立方体属于其中一个立方体堆。
给出了一个建筑的左视图和正视图。请计算有多少种建筑,符合给出的左视图和正视图。答案可能很大,只要返回它除以10^9 + 9的余数即可。
Input
第一行是整数n。第二行描述了建筑的左视图。第i个数表示了由上往下看时第i行最高的立方体堆的高度。第三行是整数m。第四行描述了建筑的正视图。第i个数表示了由上往下看时第i列最高的立方体堆的高度。
Data Constraint
对于100%的数据,1 <= n,m <= 50,所有出现的数不超过10^4。
Solution
- 对于一个左视图或正视图,如果将它的排列顺序调换也没有关系,所以我们可以从小到大排序。
- 如果我们的两个数组排好了序:
- 将其排成n*m的矩阵,每个位置取最小值,我们发现这个图形变成了很多个L型(或退化的L型)。
- 针对L型进行DP
- 每个L型显然是独立、互不影响的。对于每一个L型设F[i][j]表示做完了i行,其中有j列满足,然后枚举又多出来几列满足,其它的只要满足行至少有一个达到上限,剩下的随便选。
- 考虑容斥,枚举至少i行不满足,j列不满足。容斥系数就是
(-1)(i+j)*dg(i,j) *(d+1)(s-g(i,j)) *C(a,i)*C(b,j)
d表示这一个L型的限制,s表示总点数,g表示i行j列的总点数,a表示L型行的个数,b表示列的个数。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 51
#define ll long long
#define mo 1000000009
using namespace std;
int n,m,i,j,k,x,y,u,v,g,s,d;
int a[maxn],b[maxn],map[maxn][maxn];
ll ans,f,sum,jc[maxn],ny[maxn];
int check0(int x,int y,int c){
for(int i=y;i<=m;i++) if (map[x][i]!=c) return 0;
return 1;
}
int check1(int x,int y,int c){
for(int i=x;i<=n;i++) if (map[i][y]!=c) return 0;
return 1;
}
ll ksm(ll x,ll y){
ll s=1;
for(;y;y/=2,x=x*x%mo) if (y&1)
s=s*x%mo;
return s;
}
ll C(ll n,ll m){return jc[n]*ny[n-m]%mo*ny[m]%mo;}
int main(){
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(i=1;i<=m;i++) scanf("%d",&b[i]);
sort(a+1,a+1+n),sort(b+1,b+1+m);
if (a[n]!=b[m]){printf("0");return 0;}
for(i=1;i<=n;i++) for(j=1;j<=m;j++)
map[i][j]=min(a[i],b[j]);
jc[0]=ny[0]=1;
for(i=1;i<=max(n,m);i++) jc[i]=jc[i-1]*i%mo,ny[i]=ny[i-1]*ksm(i,mo-2)%mo;
ans=1;
for(x=0,y=0;!(x>=n&&y>=m);x=u,y=v){
d=map[x+1][y+1];
for(u=x+1;u<=n&&check0(u,y+1,d);u++); u--;
for(v=y+1;v<=m&&check1(x+1,v,d);v++); v--;
s=(u-x)*(m-y)+(n-x)*(v-y)-(u-x)*(v-y),sum=0;
for(i=0;i<=u-x;i++) for(j=0;j<=v-y;j++) {
g=i*(m-y)+j*(n-x)-i*j;
f=ksm(d,g)*ksm(d+1,s-g)%mo*C(u-x,i)%mo*C(v-y,j)%mo;
if ((i+j)&1) sum=(sum-f+mo)%mo; else sum=(sum+f)%mo;
}
ans=ans*sum%mo;
}
printf("%lld",ans);
}