题意是有N个人要买宠物,有M只猫和P只狗,人的编号是1~N,猫的编号是N+1~N+M,狗的编号是N+M+1~N+M+P;每个人都买一只猫和一只狗;
然后有Q条限制,表明对应编号的人和猫(过敏)或是猫和狗(打架)不能在一起,然后问有多少种购买方案使得N个人都满足,所谓的满足就是这个人买的猫和狗不打架,同时人不会对买的猫造成过敏。
因为N,M,P都不超过10,所以用状态压缩DP统计。
首先统计出人和猫的所有搭配方案,这个问题可以看成是一个N*M的棋盘,其中某些点(人对猫过敏)不能放置,问放置N个不互相攻击的车的方案数。
这个问题就比较好解决了,用一个整形表示猫的集合,然后按照人的编号进行dp,判断当前人和当前猫是否能在一起,能就把状态转移过去。
状态转移方程:dp[i][k|(1<<j)] += dp[i-1][k],其中k代表已经被取走的猫的集合,j是新加进来的猫。
这样就可以求出所有人和猫的组合方案。
找出dp[n][i]中,二进制刚好有N个1的i,因为这就是对应的可行的人和猫的方案,同时也对应着这个方案里面猫的编号,根据这个方案再去求出猫和狗的组合方案,方法跟上面类似,两个方案数乘起来就是当前猫集合的总方案数。
最后把所有符合条件的方案数累加起来就是答案了。
#include<cstdio>
#include<cstring>
#define LL long long
inline void getnum(int& x){
x=0;
char c=getchar();
while(c<48 || c>57) c=getchar();
while(c>=48 && c<=57){
x = x*10+c-48;
c=getchar();
}
}
int n, m, p, q, i, j, k, cnt, l[11];
bool f[1001][1001];
LL dp1[11][1<<10], dp2[11][1<<10], ans;
int main(){
while(~scanf("%d %d %d", &n, &m, &p)){
getnum(q);
memset(f, 0, sizeof(f));
while(q--){
getnum(i); getnum(j);
f[i][j]=1;
f[j][i]=1;
}
if(n>m || n>p){
puts("0");
continue;
}
memset(dp1, 0, sizeof(dp1));
//这里dp1记录人和猫的组合数
dp1[0][0]=1;
for(i=1; i<=n; i++){
for(j=0; j<(1<<m); j++){
for(k=0; k<m; k++){//遍历猫,猫的编号比实际编号少N+1
if(!f[i][k+n+1] && !(j&(1<<k))){
dp1[i][j|(1<<k)] += dp1[i-1][j];
}
}
}
}
ans=0;
for(i=0; i<(1<<m); i++){
cnt=0;
//先判断该组合是否合法,即是否恰好N只猫
for(j=0; j<m; j++){
if(i&(1<<j)){
l[++cnt]=j;//存储猫的编号(这里猫的编号比实际编号少N+1)
}
}
if(cnt!=n) continue;
memset(dp2, 0, sizeof(dp2));
dp2[0][0]=1;
//统计猫和狗的组合数
for(j=1; j<=n; j++){
for(k=0; k<(1<<p); k++){
for(int x=0; x<p; x++){//遍历狗,狗的编号比实际编号少N+M+1
if(!f[l[j]+n+1][x+n+m+1] && !(k&(1<<x))){
dp2[j][k|(1<<x)]+=dp2[j-1][k];
}
}
}
}
LL tmp=0;
for(j=1; j<(1<<p); j++){
cnt=0;
for(k=0; k<p; k++){
if(j&(1<<k)) cnt++;
}
//找出那些狗恰好N只的集合
if(cnt==n) tmp+=dp2[n][j];
}
ans += tmp*dp1[n][i];
}
printf("%lld\n", ans);
}
return 0;
}