链接:https://pintia.cn/problem-sets/1441745686294945792/problems/1441745856154955786
题意:
有n个人,n个菜,a[i][j]:第i个人对菜j的偏爱度,现在n个人轮流吃菜,起初S中有n个菜,第i个人会在还没拿走的菜中随机选一个,拿走第j个菜的概率为a[i][j]/Σ(剩余菜的偏爱度总和),然后将这个菜从集合S中删除,如果S为空则结束,否则轮到下一个继续拿菜,输出第i个人拿第j个菜的概率矩阵,n<=20 ,1<=a[i][j]<=200
状态压缩,设f[i][j][k] : 第i个人从集合j中拿走第k道菜,所得到的概率
f[i][j][k]=Σ f[i-1][j^(1<<u)][u]*a[i][j]/sum(集合j的剩余a总和) (u是上次拿走的哪道菜)
g[i][k]=Σ f[i][j][k]
注意要预处理1~2000的模998244353逆元,否则dp时现快速幂求逆元会每次均乘以一个不可忽略的log常数,会超时!
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 1<<20,mod = 998244353,M = 20;
int f[2][N][M];
int g[M][M],n,w[M][M],inv[2021],sum[M];
vector<int> seq[M];
int qmi(int a,int b){
int res=1%mod;
while(b){
if(b&1) res=(LL)res*a%mod;
a=(LL)a*a%mod;
b>>=1;
}
return res;
}
inline int get_zero(int x){
int sum=n;
while(x) sum-=x&1,x>>=1;
return sum;
}
void init(){
for(int i=0;i<1<<n;i++){
int o=get_zero(i);
seq[o].push_back(i);
}
}
void dp(){
for(int i=0;i<n;i++) f[0][(1<<n)-1][i]=g[0][i]=(LL)w[0][i]*inv[sum[0]]%mod;
for(int i=1;i<n;i++){
int s=sum[i];
for(auto j:seq[i]){
int t=0;
for(int k=0;k<n;k++)
if(!(j>>k&1))
t+=w[i][k];
s-=t;
for(int k=0;k<n;k++){
if(j>>k&1){
for(int u=0;u<n;u++){
if(!(j>>u&1)){
f[i&1][j][k]=(f[i&1][j][k]+(LL)f[(i-1)&1][(j^(1<<u))][u]*w[i][k]%mod*inv[s]%mod)%mod;
}
}
g[i][k]=(g[i][k]+f[i&1][j][k])%mod;
}
}
s+=t;
}
}
}
int main(){
for(int i=1;i<=2000;i++) inv[i]=qmi(i,mod-2);
cin>>n;
init();
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
cin>>w[i][j];
sum[i]+=w[i][j];
}
dp();
for(int i=0;i<n;i++){
if(i) cout<<"\n";
for(int j=0;j<n;j++){
if(j) cout<<" ";
cout<<g[i][j];
}
}
return 0;
}