洛谷P3158 [CQOI2011]放棋子(状压DP,容斥)

6 篇文章 0 订阅
3 篇文章 1 订阅

题目链接: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=0i1 ∑ r = 0 j − 1 \sum_{r=0}^{j-1} r=0j1 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[k1][l][r]g[a[k]][il][jr]c(nl,il)c(mr,jr)
其中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(ij,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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值