这个题目搁置了这么久,终于搞完了。
给n个人分配n个课程,已经告诉了你n个人对哪几门感兴趣,问最多有多少种分配方式
我刚开始都没找到这怎么还可以状态dp,哪来的状态转移,想用暴力DFS,果断TLE的妥妥的。
后来给殷犇发了这个题目,他还说你刷个这水题还刷得这包子劲,这题目就是后一行的状态由前一行得到,枚举当前这一行分配的状态,如果可行,就从后面的状态加过来
由于状态只是从上一行转移过来,所以可以用滚动数组,用p表示当前,则!p就为上一行的,每次结束再把p置反即可。
#include <iostream> #include <cstdio> #include <cstring> #define ll long long using namespace std; int n; int sta[22]; ll dp[2][1<<21]; int calc[22][1<<21],cnt[22]; void init() { memset(calc,0,sizeof calc); memset(cnt,0,sizeof cnt); for (int i=0;i<(1<<21);i++) //这里把只有一门课 两门。。。N门课全部用一个数组存起来,calc[i][]就代表有i个课程的状态,因为不止一种 所以开了两维。 { int tmp=0; for (int j=i;j;j>>=1) { if (j&1) tmp++; } calc[tmp][cnt[tmp]++]=i; } } int main() { int t,a; scanf("%d",&t); init(); while (t--) { scanf("%d",&n); for (int i=0;i<n;i++) { sta[i]=0; for (int j=0;j<n;j++) { scanf("%d",&a); if (a==1) { sta[i]+=1<<j;//存入学生感兴趣的课程的状态。 } } } int p=0,all=(1<<n)-1; //memset(dp,0,sizeof dp); dp[1][0]=1; for (int i=0;i<n;i++)//从第0个人循环到第n-1个人 { for (int k=0,s=calc[i+1][k];k<cnt[i+1]&&calc[i+1][k]<=all;s=calc[i+1][++k])//当前是第i号人 则状态必定是calc[i+1][]的状态,比如第三个人就只分配三个课程即可,一旦可行 就从分配了两个课程的状态那里转移过来。 { dp[p][s]=0; for (int j=0;j<n;j++)//逐个试探第j号课程是否可以 { //dp[p][k]=0; if (((1<<j)&sta[i])==0) continue; if ((1<<j)&s) { //dp[p][k]+=1; dp[p][s]+=dp[p^1][(1<<j)^s];//一旦该状态可以,则从上一次的不含新分配的课程的那里转移过来 } //else // dp[p][k]+=dp[p^1][k]; } } p^=1; } printf("%lld\n",dp[p^1][all]); } return 0; }