问题:有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;
}