poj2441 Arrange the Bulls -- 状压dp

Arrange the Bulls


问题:有n头牛,m个位置,每头牛都有1个或几个喜欢的位置,每个位置只能住一头牛。
问:有多少种安排的方法能够使每头牛都住在自己喜欢的位置。


我先用了dfs(TLE),又用了bfs(MLE),最后才用的是dp。

dfs

dfs版:每头牛有他自己喜欢的位置,从第一头牛开始遍历到最后一头牛,如果它喜欢的位置是空的我们就能将它放进去(vis[edge[i].to = 1),若已经有了牛在这个位置,我们就跳过它的这个喜欢的位置(注意要vis[edge[i].to] = 0;,在回到这头牛的时候我们应该归零,不影响前面的牛的选择)。

#include<iostream>
#include<cstdio>
using namespace std;
int head[100], vis[100], idx = 1, ans = 0;
struct E{
    
    
	int to, next;
} edge[410];
void add(int x, int y) {
    
    
	edge[idx].to = y, edge[idx].next = head[x], head[x] = idx++;
}
void dfs(int u, int n) {
    
    
	if(u == n+1) {
    
    
		ans++;
		return ;
	}
	for(int i=head[u]; i; i=edge[i].next) {
    
    
		if(!vis[edge[i].to]) {
    
    
			vis[edge[i].to] = 1;
			dfs(u+1, n);
			vis[edge[i].to] = 0;
		}
	}
}
int main() {
    
    
	int n, m, p, x;
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) {
    
    
		scanf("%d", &p);
		for(int j=0; j<p; j++) {
    
    
			scanf("%d", &x);
			add(i, x);
		}
	}
//	for(int i=1; i<=n; i++) {
    
    
//		for(int j=head[i]; j; j=edge[j].next) {
    
    
//			printf("%d %d\n", i, edge[j].to);
//		}
//	}
	dfs(1, n);
	printf("%d", ans);
	return 0;
}

bfs

别太注意dfs的名字,我在上面代码上改的。
这里用了一下状态压缩,就是把每个位置看成二进制上的位置,被选了就是1,没被选就是0,就可以用一个数字来表示现在的状态了。然后我们就用队列存状态和第几头牛,第i头牛的状态都是从第i-1头牛的到的(有点dp的味道了),对第i-1头牛的每种状态,第i头牛都要验证是否能够将i放入它喜欢的位置,可以的话,就成了第i头牛的状态。

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int head[100], vis[100], idx = 1, ans = 0;
struct E{
    
    
	int to, next;
} edge[410];
void add(int x, int y) {
    
    
	edge[idx].to = y, edge[idx].next = head[x], head[x] = idx++;
}
void dfs(int n) {
    
    
	int t = 1, v;
	pair<int, int> u;
	queue<pair<int, int> > Q;
	Q.push(make_pair(0, 1));//第0
	while(!Q.empty()) {
    
    
		u = Q.front(); Q.pop();
		if(u.second != t) t++;//只有第i-1头牛的状态都枚举完了,才能轮到下一头牛。
		if(t > n) break;//所有的牛都枚举完了。
		for(int i=head[t]; i; i=edge[i].next) {
    
    
			if(!((u.first>>edge[i].to) & 1)) {
    
    //牛喜欢的位置是否为0.
				Q.push(make_pair(u.first^(1<<edge[i].to), t+1));
			}
		} 
	}
	printf("%d", Q.size()+1);
}
int main() {
    
    
	int n, m, p, x;
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) {
    
    
		scanf("%d", &p);
		for(int j=0; j<p; j++) {
    
    
			scanf("%d", &x);
			add(i, x);
		}
	}
	dfs(n);
	return 0;
}

上面两个代码都没过,下面才是主题。

dp

状态:dp[i],i表示所有位置组成的10进制数,dp[i]存储着当前状态下的方案数。
状态的转移:i 有 n-1 个1,才能的到n个1的状态。
一段一段讲代码吧。
下面这段代码将每头牛喜欢的位置存在s数组中。s[i][j]--;的用处是更好的从二进制的0位置开始表示。

for(int i=1; i<=n; i++) {
    
    
		scanf("%d", &s[i][0]);
		for(int j=1; j<=s[i][0]; j++) {
    
    
			scanf("%d", &s[i][j]);
			s[i][j]--;
		}
	}

循环更新dp数组。i表示第i头牛,k表示位置组成的10进制数,s[i][j]表示第i头牛喜欢的位置,__builtin_popcount(k)内置函数求k的二进制中1的个数。我们第i头牛必须是从i-1头牛的状态变过来的,所以要从已经选了i-1个位置的状态转移过来。

for(int i=1; i<=n; i++) {
    
    
		for(int k=0; k<(1<<m); k++) {
    
    
			if(__builtin_popcount(k) == i-1)
			for(int j=1; j<=s[i][0]; j++) {
    
    
				if(!((k>>s[i][j])&1)) //判断喜欢的位置是否还是空的
				dp[k | (1<<s[i][j])] += dp[k];
			}
		}
	}

总代码

#include<iostream>
#include<cstdio>
using namespace std;
int dp[1<<20+5], s[25][25];
int main() {
    
    
	int n, m, t, e = 0, ans = 0;
	scanf("%d%d", &n, &m);
	if(n > m){
    
    
		printf("%d", 0);
		return 0;
	}
	for(int i=1; i<=n; i++) {
    
    
		scanf("%d", &s[i][0]);
		for(int j=1; j<=s[i][0]; j++) {
    
    
			scanf("%d", &s[i][j]);
			s[i][j]--;
		}
	}
	dp[0] = 1; //初始化
	for(int i=1; i<=n; i++) {
    
    
		for(int k=0; k<(1<<m); k++) {
    
    
			if(__builtin_popcount(k) == i-1)
			for(int j=1; j<=s[i][0]; j++) {
    
    
				if(!((k>>s[i][j])&1)) 
				dp[k | (1<<s[i][j])] += dp[k];
			}
		}
	}
	//最后找找有n个位置是1的状态,然后加起来,就是答案了。
	for(int i=0; i<(1<<m); i++) {
    
    
		if(__builtin_popcount(i) == n) {
    
    
			ans += dp[i];
		}
	}
	printf("%d\n", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45363113/article/details/106737206
今日推荐