题目阐述:
给定n个座位,n个人,每个人可以做n个位置中的任意一个,P[i][j]代表第i个人做第j个位置获得的分数,求有多少种排列方式使得获得的分数大于等于M。
这道题跟数位dp的思想很像,都是穷举可能的方式,不过数位DP由于前缀的影响可以记忆化,这道题由于n较小,可以直接状态压缩.
定义状态d[i][s][t]代表已经放了i个座位,放的人数集合为s,获得分数为t的排列的数量。
然后每次暴力枚举每个位置可能会放的人
d[i][s | (1 << j)][t+ p[j][i]] +=d[i-1] [s] [t];
重点是如何实现:
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #define maxn 13 using namespace std; int d[1<<(maxn)][505]; //表示到达状态s时产生的最大能量 int n,m; int P[maxn][maxn]; int ans; void init() { ans=0; memset(d,0,sizeof(d)); } //GCD //求最大公约数 //O(logn) int gcd(int a, int b) { if (b == 0) return a; else return gcd(b, a%b); } int isok(int i) { int t=0; while(i) { if(i&1) t++; i>>=1; } return t; } void solve() { int tot=(1<<n)-1; d[0][0]=1; //其实可以理解成d[-1][0][0],否则下面就需要特殊处理i等于0 for(int i=0;i<n;i++) //代表第i个位置 { for(int s=0;s<=tot;s++) //遍历所有状态 { if(isok(s)!=i) continue ;//检测哪些是前i-1个位置的状态 for(int t=0;t<=m;t++) //遍历所有获得的分数 { if(!d[s][t]) continue; //检测哪些是前i-1个位置获得的分数 for(int j=0;j<n;j++) //枚举第i个位置可能放的人 { if( s & (1<<j) ) //检测前i-1个位置是否放过 continue; int state= s | (1<<j); int MM=min(m,t+P[j][i]); d[state][MM]+=d[s][t]; } } } } ans=d[tot][m]; } int main() { // freopen("test.txt","r",stdin); int fac[maxn]; fac[0]=1; for(int i=1;i<maxn;i++) fac[i]=fac[i-1]*i; int t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); init(); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { scanf("%d",&P[i][j]); } solve(); int d = gcd(fac[n], ans); if (ans == 0) printf("No solution "); else printf("%d/%d ", fac[n]/d, ans/d); } return 0; }
bfs实现:
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <queue> #define maxn 13 using namespace std; int d[1<<(maxn)][505]; //表示到达状态s时产生的最大能量 int n,m; int P[maxn][maxn]; int ans; int gcd(int a, int b) { if (b == 0) return a; else return gcd(b, a%b); } int bit(int i) { int t=0; while(i) { if(i&1) t++; i>>=1; } return t; } struct node { int s,t,cnt=0; }; int visit[1<<maxn][505]; void bfs() { queue < node > que; memset(visit,0,sizeof(visit)); int tot=(1<<n)-1; node start,last; start.s=0; start.t=0; start.cnt=0; que.push(start); visit[start.s][start.t]=1; d[0][0]=1; while(!que.empty()) { node cur=que.front(); que.pop(); for(int i=0;i<n;i++) { if(cur.s & (1<<i)) continue; node next ; next.s= cur.s | (1<<i); next.cnt=cur.cnt+1; next.t= min(m,cur.t+P[i][cur.cnt]); //将第i个人放在当前位置,然后才会加1 d[next.s][next.t] += d[cur.s][cur.t]; if(visit[next.s][next.t]) //保证只进队一次 continue; que.push(next); visit[next.s][next.t]=1; } } ans=d[tot][m]; } int main() { // freopen("test.txt","r",stdin); int fac[maxn]; fac[0]=1; for(int i=1;i<maxn;i++) fac[i]=fac[i-1]*i; int t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); ans=0; memset(d,0,sizeof(d));; for(int i=0;i<n;i++) for(int j=0;j<n;j++) { scanf("%d",&P[i][j]); } bfs(); int d = gcd(fac[n], ans); if (ans == 0) printf("No solution "); else printf("%d/%d ", fac[n]/d, ans/d); } return 0; }