icpc网络赛第二场K Meal
题意:
有n个人,n个菜,
现在n个人轮流吃菜,起初S中有n个菜,第i个人会在还没拿走的菜中随机选一个,拿走第j个菜的概率为ai,j∑k∈Sai,k\frac{a_{i,j}}{\sum_{k∈S}a_{i,k}}∑k∈Sai,kai,j,然后将这个菜从集合S中删除,如果S为空则结束,否则轮到下一个继续拿菜
让你输出第i个人拿第j个菜的概率
n<=20 ,ai,ja_{i,j}ai,j<=100
题解:
比赛时想好大体思路,好不容易把A写完,调了半天,最后没时间做K了
题意很好理解,对于不同的拿取方式,对于第i个人的情况是不同的,不难看出n很小才20,所以我们可以状压dp
我们枚举二进制,1表示这个菜被取了,0表示还未取
对于一个状态state,假设共cnt个位置为1,因为是顺序吃菜,所以就是求第cnt个人的吃菜概率,枚举其中为1的位置,相当于是吃的第j种菜
用f[i]表示当前这个选菜局面的概率,i的二进制状态下表示菜的选取情况
利用之前的f[]来转移现在的ans[][],用现在的选取状态来更新当前的f[]
这样得到转移方程:
ans[cnt][j]=(ans[cnt][j]+f[i^(1ll<<j)]*a[cnt][j]%mod*inv[sum[cnt]-tot+a[cnt][j]])%mod;
f[i]=(f[i]+f[i^(1ll<<j)]*a[cnt][j]%mod*inv[sum[cnt]-tot+a[cnt][j]])%mod;
这样复杂度为O(n*2n),注意因为a很小,所以求逆元不要用快速幂,会超时,直接O(n)预处理好即可
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
#define x first
#define y second
typedef pair<int,int> pii;
const int N=21;int dp[N][1<<N];
int a[N][N];
int f[1<<N];
int ans[N][N];
int sum[N];
int inv[30000000];
const int mod= 998244353;
int qmi(int a,int b)
{int res=1;while(b){if(b&1) res=res*a%mod;b>>=1;a=a*a%mod;}return res;
}
void init(){inv[1]=1;for(int i=2;i<=2000;++i)inv[i]=mod-(long long)mod/i*inv[mod%i]%mod;
}
signed main()
{int n;cin >> n;init();for(int i=1;i<=n;i++) {for(int j=0;j<n;j++) cin >> a[i][j],sum[i]=(sum[i]+a[i][j])%mod;}f[0]=1;for(int i=1;i<1<<n;i++){int cnt=0;int tot=0;for(int j=0;j<n;j++)if(i>>j&1) cnt++;for(int j=0;j<n;j++)if(i>>j&1) tot=(tot+a[cnt][j])%mod;for(int j=0;j<n;j++){if(i>>j&1){
// cout<<"sum[cnt]-?tot+a[cnt][j]="<<sum[cnt]-tot+a[cnt][j]<<endl;ans[cnt][j]=(ans[cnt][j]+f[i^(1ll<<j)]*a[cnt][j]%mod*inv[sum[cnt]-tot+a[cnt][j]])%mod;f[i]=(f[i]+f[i^(1ll<<j)]*a[cnt][j]%mod*inv[sum[cnt]-tot+a[cnt][j]])%mod;}}
// cout<<f[i]<<endl;}for(int i=1;i<=n;i++){for(int j=0;j<n;j++)if(j==0)cout<<ans[i][j];else cout<<" "<<ans[i][j];cout<<endl;}
}