题目链接:https://www.luogu.com.cn/problem/P3158
洛谷P3158 [CQOI2011]放棋子
状压DP
这道题的思想方法和之前那道P2051中国象棋类似。
同理,因为前面棋子的放置方案会影响到后面棋子的放置方案,我们将每种颜色棋子的放置划分为一个状态,我们需要表示出放置了前k种颜色的棋子时的棋盘状态。
我们用数组f[k][i][j]表示用前k种颜色的棋子占领了i行与j列的方案种类数,那么可以很容易写出状态转移方程如下:
f[k][i][j]=
∑
l
=
0
i
−
1
\sum_{l=0}^{i-1}
∑l=0i−1
∑
r
=
0
j
−
1
\sum_{r=0}^{j-1}
∑r=0j−1
f
[
k
−
1
]
[
l
]
[
r
]
∗
g
[
a
[
k
]
]
[
i
−
l
]
[
j
−
r
]
∗
c
(
n
−
l
,
i
−
l
)
∗
c
(
m
−
r
,
j
−
r
)
f[k-1][l][r]*g[a[k]][i-l][j-r]*c(n-l,i-l)*c(m-r,j-r)
f[k−1][l][r]∗g[a[k]][i−l][j−r]∗c(n−l,i−l)∗c(m−r,j−r)
其中g[a[k]][i][j]表示用a[k]个棋子填满i行j列的方案数,条件需满足(i-l)*(j-r)>=a[k]
那么一个关键问题就是计算g[a[k]][i][j]。
直接计算g函数比较繁琐,考虑容斥g[k][i][j]=
c
(
i
∗
j
,
k
)
−
c(i*j,k)-
c(i∗j,k)−
∑
l
=
1
i
\sum_{l=1}^{i}
∑l=1i
∑
r
=
1
j
\sum_{r=1}^{j}
∑r=1j
g
[
k
]
[
l
]
[
r
]
∗
c
(
i
,
l
)
∗
c
(
j
,
r
)
g[k][l][r]*c(i,l)*c(j,r)
g[k][l][r]∗c(i,l)∗c(j,r)。
最后直接递推计算即可。
但我在实际做题时遇到了一个问题,我一开始用预处理阶乘的方式计算组合数,但是这样处理计算组合数时的复杂度太大(计算时还需包括取模运算),一直过不了最后一个点,实际上这题的数据范围较小,只需用杨辉三角递推处理出1-900的组合数即可,运算效率比阶乘预处理要高。
实际上在不同的场合选用不同的方式处理组合数是非常必要的,详情可以看我的这篇博客:
组合数的几种常规求法
#include<bits/stdc++.h>
#define next next_
#define y1 yy
#define hash hash_
#define size size_
#define complex complex_
using namespace std;
using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const ll mod=1e9+9;
const ll base=131;
const double pi=acos(-1.0);
int _;
ll n,m,K,c[910][910];
ll a[11],f[11][33][33],g[910][33][33],ans;
ll fac[910],inv[910];
ll pow(ll a,ll b){
ll sum=1;
while(b){
if(b&1) sum=sum*a%mod;
a=a*a%mod;
b>>=1;
}
return sum;
}
void init(){
for(ll i=0;i<=900;i++) c[i][0]=c[i][i]=1;
for(ll i=1;i<=900;i++)
for(ll j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
void work(){
init();
scanf("%lld%lld%lld",&n,&m,&K);
ll N=0;
for(ll i=1;i<=K;++i) scanf("%lld",&a[i]),N=max(N,a[i]);
f[0][0][0]=1;
for(ll k=1;k<=N;++k)
for(ll i=1;i<=n;++i)
for(ll j=1;j<=m;++j){
if(i*j>=k){
g[k][i][j]=c[i*j][k];
for(ll l=i;l>=1;--l)
for(ll r=j;r>=1;--r){
if(l<i||r<j){
g[k][i][j]-=g[k][l][r]*c[i][l]%mod*c[j][r]%mod;
while(g[k][i][j]<0) g[k][i][j]+=mod;
}
}
}
}
for(ll k=1;k<=K;++k)
for(ll i=1;i<=n;++i)
for(ll j=1;j<=m;++j){
for(ll l=i-1;l>=0;--l)
for(ll r=j-1;r>=0;--r){
if((i-l)*(j-r)>=a[k]){
f[k][i][j]+=f[k-1][l][r]*g[a[k]][i-l][j-r]%mod*c[n-l][i-l]%mod*c[m-r][j-r]%mod;
f[k][i][j]%=mod;
}
}
}
for(ll i=1;i<=n;++i)
for(ll j=1;j<=m;++j) ans+=f[K][i][j];
printf("%lld",ans%mod);
}
int main(){
// scanf("%d",&_);
_=1;
while(_--){
work();
}
return 0;
}