1. 程式人生 > >牛客練習賽31 C 無畏死靈術士莉蓮娜與鎖鏈面紗(dfs + 期望dp)

牛客練習賽31 C 無畏死靈術士莉蓮娜與鎖鏈面紗(dfs + 期望dp)

 

 

太久沒有做期望/概率dp,已鏽。。。

大概就是說給你一個1到n的全排列,然後每次隨機選擇一個數字在不改變其他數字相對位置的前提下,把比他小的數字放在他前面,大的在後面。問期望幾次能夠使得這個序列有序。

由於是求期望,所以顯然是要倒著求。令dp[x]表示狀態x下變得有序的期望步數。那麼顯然有轉移方程:

                                          \large dp[x]=(\frac{t}{n}*dp[x]+\frac{1}{n}*\sum_{x->y}dp[y])+1

其中y表示x可以轉移到的狀態。也即,x這個狀態可以選擇下一步走到任意一個可以轉移狀態也可以還是自己原來的狀態不變。這樣我們移項把dp[x]都放到左邊,那麼有轉移方程:

                                         \large dp[x]=\frac{n}{n-t}*(1+\frac{1}{n}*\sum_{x->y}dp[y])

那麼現在問題的關鍵就是這個狀態如何去確定。看到這裡n最多為20,所以我們大膽的猜測,這個總的不重複的狀態一定不會很多。我們用dfs暴力出所有的狀態,發現當n為20的時候,狀態數最多,為5W+,程式也能在規定時間內跑完,完全在我們的承受範圍內。在這裡,我用了map對映vector的方式去重,但是其實用字串雜湊的話速度更加可觀。具體見程式碼:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define pi 3.141592653589793
#define mod 998244353
#define LL long long
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define sf(x) scanf("%d",&x)
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)

using namespace std;

const int N = 21;

map<vector<int>,int> mp;
int n,inv[N]; bool v[N];
vector<int> a;

inline void init()
{
    inv[1]=1;
    for(int i=2;i<N;i++)
        inv[i]=(mod-mod/i)*(LL)inv[mod%i]%mod;
}

int dfs(vector<int> a)
{
    if (mp[a]) return mp[a];
    vector<int> b; b.resize(n);
    int ans=0,t=0;
    for(int i=0;i<n;i++)
    {
        if (v[i]) {t++;continue;}
        int l=0,r=i+1;b[i]=i;
        for(int j=0;j<n;j++)
            if (a[j]<i) b[l++]=a[j];
            else if (a[j]>i) b[r++]=a[j];
        v[i]=1;
        if (a==b) t++;
        else ans=(ans+(LL)dfs(b)*inv[n]%mod)%mod;
        v[i]=0;
    }
    return mp[a]=(LL)(ans+1)*n%mod*inv[n-t]%mod;
}

int main()
{
    init(); sf(n);
    a.resize(n);
    for(int i=0;i<n;i++) sf(a[i]),a[i]--;
    printf("%d\n",dfs(a));
    return 0;
}