vijos1286座位安排(状压dp加组合数加gcd)

背景

快要期中考试了!老师需要hzy帮他排考试的座位。。。

描述

考场里的座位恰好有n行m列,并且恰好有n*m位考生在这个考场里面考试,也就是说,所有的座位上都有考生。hzy根据学校记载,有k位考生可能作弊,因此hzy不能让他们之中的任何两个人做在相邻的座位上!所谓相邻的座位,即在同一行相邻列或者在同一列的相邻行的座位。hzy准备这样安排座位,首先随机选择一种方案,如果这种方案是合法的,就用这种方案,否则重新选择。你的任务是计算,他得到一个合法方案时,需要的期望选择次数。

格式

输入格式

输入文件为一行,仅包含三个整数n,m和k。

输出格式

如果不存在合法的方案,则输出文件seating.out中应该包含Impossible!,否则输出一个分数p/q,表示期望选择次数(即平均次数),这里p和q应该是互质的。

样例1

样例输入1

1 4 3

 
 

样例输出1

Impossible!

 
 

样例2

样例输入2

2 3 2

 
 

样例输出2

15/8

 
 

提示

1≤n≤80,1≤m≤80,1≤n*m≤80
0≤k≤20,并且k≤n*m


分析:若选择f[i][j][k]表示到第i行第j列选k个有多少种方案, 那么f[i][j][k] 由f[i-1][j][k1] 和 f[i][j-1][k2]得来,但是座位两两之间不相连,行列之间的状态很难表示,所以选择用二进制表示状态。


设f[i][j][k] 表示 目前到第i行已选k个,第i行状态为j的方案数,

显然 j 要满足(j & (j>>1)) 为 0,i行自身的约束考虑好了,但是

第i行还要受到i-1行的约束,于是我们枚举第i-1行的状态,第i-1

行与第i行不冲突的条件为(t & (t>>1)) == 0和( t & j) == 0

(t为第i-1行的状态),然后我们可以得出转移方程:

f[i][j][k] += f[i-1][t][j-bit[k]](bit[k]为k的二进制中1的个数)

接下来分析k, k的最大值为2^z-1,z为n,m中较小的数,因为n*m<=80,所以z最大为8而已

因为题目要求最简分数, 所以要求ans与c[n*m][k]的最大公因数




第一次做状压dp的题QAQ,经验还是不足,要多做做啊!!

贴代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll dp[81][21][260], ans, c[81][81], ans1, d;
int bit[260];
ll gcd(ll a, ll b){
	return !b?a:gcd(b,a%b);
}
int getbit(int x){
	int ans = 0;
	while(x){
		x -= x&-x;
		ans++;
	}
	return ans;
}
int main(){
	int n, m, p, i, j, k, t;
	scanf("%d%d%d", &n, &m, &p);
	if(m > n) swap(n,m);
	dp[0][0][0] = 1;
	for(i = 0; i < (1<<m); i++) bit[i] = getbit(i);
	for(i = 1; i <= n; i++){
		for(j = 0; j <= p; j++){
			for(k = 0; k < (1<<m); k++){
				if(!(k&(k>>1)) && j >= bit[k]){//这一行的情况 
					for(t = 0; t < (1<<m); t++){//枚举上一行的情况 
						if(!(t&k) && !(t&(t>>1)) && bit[t]<=j-bit[k]){
							dp[i][j][k] += dp[i-1][j-bit[k]][t];
						}
					}
				}
			}
		}
	}
	for(j = 0; j < (1<<m); j++)
		ans += dp[n][p][j];
	if(!ans){
		printf("Impossible!");
		return 0;
	} 
	for(i = 0; i <= n*m; i++){
		c[i][0] = 1;
		for(j = 1; j <= i; j++){
			c[i][j] = c[i-1][j] + c[i-1][j-1];
		}
	}
	ans1 = c[n*m][p];
	d = gcd(ans, ans1);
	ans /= d;
	ans1 /= d;
	printf("%lld/%lld", ans1, ans);
	return 0;
} 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值