Hdu 3811 状态压缩 DP

2 篇文章 0 订阅

题意:给定数N(N<=17),求一个N位数,每位可选1…N中的一个,且每位不同,并且满足给定的条件,求方案数。给定条件为m个:x y 表示第x位为y的满足要求。

满足m中的一个条件即正确。

算法:DP 状态压缩

分析:

首先不考虑条件的总方案数为N!

我们先算不满足条件的方案,因为m个条件之间是或者 关系,有重叠。算取反面较为简单。即将题意抽象成:
      有N个排列有序的集合,每个集合中原有元素1…N,但是有一些元素不能取(即为m个条件限制),求从每个集合选出一个元素,且所有选出元素不重复的方案数。

 

因为N<=17 搜索显然是要超时的,这个时候我们考虑到只有17个数字,2^17=131072。

每个数字只有两种状态,取或者不取,所以可以用二进制来表示当前的状态。

有状态转移方程: f[i][j]-> f[i+1][now] | 满足第i+1个集合可以取数 k,并且状态j中没有取数k,即 j&(1<<(k-1))==0.  Now表示加上k的状态.

 

再注意到数据的范围需要long long

并且最后输出若用%lld 便WA ,%I64d便AC

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#define rep(i,n) for(int i=0;i<n;i++)
#define rrep(i,n) for(int i=1;i<=n;i++)
typedef long long ll;
using namespace std;
const int maxn=131072+10;
int map[20],n,m,ctrl,x,y;
ll	f[18][maxn],fact[19];
bool exist[18][18];
ll calc(int n)
{
	if(fact[n])return fact[n];
	return (calc(n-1)*n);
}
int main()
{
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	scanf("%d",&ctrl);
	map[1]=1;
	for(int i=2;i<=17;i++)map[i]=map[i-1]<<1;//map[i]表示 只取数i
	fact[1]=1;
	rep(cc,ctrl){
		scanf("%d%d",&n,&m);
		fact[n]=calc(n);
		memset(exist,1,sizeof(exist));
		memset(f,0,sizeof(f));
		int fans=(1<<n)-1;//最终状态 即N个数全部取得 二进制下为 N个1
		rep(i,m){
			scanf("%d%d",&x,&y);
			exist[x][y]=0;
		}
		f[0][0]=1;
		for(int i=0;i<n;i++)//因为i+1的状态都有i得来 所以可以将数组优化成1维
			for(int j=0;j<=fans;j++)//优化成1维后需 downto 枚举
			{
			    if(f[i][j]==0)continue;
				for(int k=1;k<=n;k++)
					if((exist[i+1][k])&&((j&map[k])==0))//第i+1位可以取k 并且状态j中不包含k
					{
						int now=j|map[k];
						f[i+1][now]+=f[i][j];
					}
			}
		printf("Case %d: %I64d\n",cc+1,fact[n]-f[n][fans]);//此处改为%lld 便WA了
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值