POJ 2441 状态压缩+动态规划(好题)

Description

Farmer Johnson’s Bulls love playing basketball very much. But none of them would like to play basketball with the other bulls because they believe that the others are all very weak. Farmer Johnson has N cows (we number the cows from 1 to N) and M barns (we number the barns from 1 to M), which is his bulls’ basketball fields. However, his bulls are all very captious, they only like to play in some specific barns, and don’t want to share a barn with the others.

So it is difficult for Farmer Johnson to arrange his bulls, he wants you to help him. Of course, find one solution is easy, but your task is to find how many solutions there are.

You should know that a solution is a situation that every bull can play basketball in a barn he likes and no two bulls share a barn.

To make the problem a little easy, it is assumed that the number of solutions will not exceed 10000000.

Input

In the first line of input contains two integers N and M (1 <= N <= 20, 1 <= M <= 20). Then come N lines. The i-th line first contains an integer P (1 <= P <= M) referring to the number of barns cow i likes to play in. Then follow P integers, which give the number of there P barns.

Output

Print a single integer in a line, which is the number of solutions.

Sample Input

3 4
2 1 4
2 1 3
2 2 4

Sample Output

4

题目解析

这道题题意很好理解,有n个队伍,m个球场,然后每个队伍有各自喜欢的场地,问有多少种组合方案。
很好想到用状态压缩来解决:初始化为(1<<m)-1,然后dfs来判断,如果前面n个队伍都处理完了,res++。但是,这个会超时,复杂度太高,O(nm2^m).
但还是贴出这个TLE的代码给初学者入个门,因为这是没有优化过的状态压缩

代码(TLE)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<queue>
#include<vector>
#include<algorithm>
#include<utility>
#include<cmath>
#include<limits.h>
#include<math.h>
#include<map>
#include<climits>
#include<numeric>
#include<ctype.h>
#include<fstream>
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
typedef unsigned long long Ull;
int n,m; 
vector<vector<int> > myV(20);
int res=0;
bool dp[20][1<<20];
void dfs(int step,int field){
	if(step==n){
		res++;
		return;
	}
	if(dp[step][field]>0){
		res+=dp[step][field];
		return;
	}
	for(int i=0;i<myV[step].size();i++){
		if(field>>(myV[step][i]-1)&1){
			int t1=res;
			dfs(step+1,field&~(1<<(myV[step][i]-1)));
			if(res>t1){
				//记忆化,到了这一步之后他有多少种组合方案,就不必再一直dfs到末尾了 
				dp[step][field]=res-t1;
			}
		}
	}
}
int main() {
	while(scanf("%d%d",&n,&m)!=EOF){
		int size;
		res=0;
		for(int i=0;i<n;i++){
			scanf("%d",&size);
			myV[i].resize(size);
			for(int j=0;j<size;j++){
				scanf("%d",&myV[i][j]);
				//cout<<myV[i][j]<<endl;
			}
		}
		memset(dp,0,sizeof(dp));
		dfs(0,(1<<m)-1);
		printf("%d\n",res);
	}
	return 0;
}

那么如何优化呢?
就需要清楚动态规划这个万能钥匙了,设定dp数组,由于二维数组会超出内存,所以这道题采用一维数组来解决(一维数组不懂的话,可以先去看看背包问题的一维数组的思想)
最外层循环为0~n-1,这一层是依次考虑这n头牛,保证最终集合里面包括这n头牛。
第二层循环是从(1<<m)-1~0,这一层必须倒序(一维数组的铁规距),是利用上一头牛的组合来为该头牛进行更新,换句话说,就是进行该层循环前的dp数组是前面牛能组成集和S的情况,比如dp[S]就是集和S中已经包括了前面牛组成的情况。
因此必须当前面牛组合成集和S时的dp数组值不为零才能继续往下走。这就到了第三层循环了,这一层循环是为当前牛进行选择场地。能选择场地的条件有:1.前面牛都没用过这个场地,2. dp[S]!=0(保证了前面牛的存在),然后进行更新即可,还要注意,当更新完每一个S之后,一定要清零该S,因为这时的S是前面牛(不包括该牛)的组合,而下一头牛所用的dp应该是包括当前牛的情况,所以必须清零。

最后,统计dp数组中的值即可。

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const double eps = 1e-6;
int field[30][30];
int dp[(1<<20)+10];
int n,m;
int main() {
	while(scanf("%d%d",&n,&m)!=EOF) {
		int size;
		int res=0;
		memset(dp,0,sizeof(dp));
		memset(field,0,sizeof(field));
		for(int i=0; i<n; i++) {
			scanf("%d",&size);
			int x;
			for(int j=0; j<size; j++) {
				scanf("%d",&x);
				field[i][--x]=1;
			}
		}
		
		dp[0]=1;
		for(int i=0;i<n;i++){
			for(int j=(1<<m)-1;j>=0;j--){
				if(dp[j]!=0){
					for(int k=0;k<m;k++){
						if(j>>k&1||!field[i][k]){
							continue;
						}
						dp[j|1<<k]+=dp[j];
					}
					dp[j]=0;
				} 
			}
		}
		for(int i=0;i<1<<m;i++){
			res+=dp[i];
		}
			
		printf("%d\n",res);
	}
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值