https://cn.vjudge.net/problem/ZOJ-3777
题目
某人出题,有N道,觉得题目难度按题目顺序增加很没意思。他发现将编号为$i$的题目放到$j$号位置能增加$P_{ij}$的趣味值,于是他将题目随机打乱,计算趣味值,如果达不到他想要的趣味值M就将题目重新随机打乱,如此循环。求他打乱次数的期望。
输入N (1 <= N <= 12)、M (1 <= M <= 500).和矩阵Pij (0 <= Pij <= 100),输出一个不可约分的分数。如果永远都达不到,输出“No solution”。
Sample Input
2
3 10
2 4 1
3 2 2
4 5 3
2 6
1 3
2 4
Sample Output
3/1
No solution
题解
如果知道概率,那么$1/$概率就能得到期望
因此就是
\[\frac{1}{\frac{符合条件的情况}{总情况}}\]
总情况是$N!$,可以直接计算,符合条件的情况可以用DP
N<=12,所以可以用状态压缩dp,设$dp[k][F]$为选了k表示的题目,趣味值为F的种类个数,于是可以得到
\[dp[0][0]=1\]
\[dp[k][F]=\sum{dp[k\oplus x][F-P_?]}\]
其中$P_?$是当前问题在这个位置能加的趣味值
由于要计算大于等于M的种类数,于是改变一下,将大于M的都设为M
\[dp[k|x][F+P_?]+=dp[k][F]\]
这样设的原因是 $k|x>k>k \oplus x$,$\oplus$表示异或
AC代码
#include<cstdio>
#include<cstring>
#include<cmath>
#define REP(r,x,y) for(register int r=(x); r<(y); r++)
#define REPE(r,x,y) for(register int r=(x); r<=(y); r++)
#define PERE(r,x,y) for(register int r=(x); r>=(y); r--)
#ifdef sahdsg
#define DBG(...) printf(__VA_ARGS__), fflush(stdout)
#else
#define DBG(...) (void)0
#endif // sahdsg
using namespace std;
typedef long long LL;
#define MAXN 30000007
int n,m;
int p[12][12];
int dp[1<<12][507];
inline int cnt(int i) {
i = (i&0x55555555)+((i>>1)&0x55555555);
i = (i&0x33333333)+((i>>2)&0x33333333);
i = (i&0x0f0f0f0f)+((i>>4)&0x0f0f0f0f);
i = (i&0x00ff00ff)+((i>>8)&0x00ff00ff);
i = (i&0x0000ffff)+((i>>16)&0x0000ffff);
return i;
}
int f[13];
inline int gcd(int a, int b) {
return b==0? a: gcd(b, a%b);
}
int main() {
f[0]=1;
REPE(i,1,12) f[i]=f[i-1]*i;
int t; scanf("%d", &t);
while(0<t--) {
scanf("%d%d", &n, &m);
REP(i,0,n) {
REP(j,0,n) {
scanf("%d", &p[i][j]);
}
}
memset(dp,0,sizeof dp);
dp[0][0]=1;
REP(i,0,(1<<n)-1) {
int ones=cnt(i);
REPE(j,0,m) if(dp[i][j]) {
REP(x,0,n) if(!(i&(1<<x))){
int k=j+p[x][ones]; if(k>m) k=m;
dp[i|(1<<x)][k]+=dp[i][j];
}
}
}
int ans=dp[(1<<n)-1][m];
if(!ans) puts("No solution");
else {
int ans2=f[n];
int g=gcd(ans,ans2);
printf("%d/%d\n", ans2/g, ans/g);
}
}
return 0;
}