1. 程式人生 > 實用技巧 >Luogu3750 [六省聯考2017]分手是祝願

Luogu3750 [六省聯考2017]分手是祝願

https://www.luogu.com.cn/problem/P3750

期望\(DP\)

運用貪心的策略,如果我們想要儘快關閉所有的燈,顯然從編號大的到編號小的關更優

那麼我們可以計算出關燈的最小次數\(cnt\)

如果\(cnt \le k\),顯然答案就是\(cnt\)

如果\(cnt > k\),我們考慮\(dp\)

由於不同的鍵的效果是互不相同的,不存在一個鍵被代替的情況

\(dp_i\)表示從最少需要按\(i\)個鍵轉移到最少需要按\(i-1\)個鍵的期望按鍵數量

\[dp_i=\frac{i}{n}+\frac{n-i}{n} (dp_{i+1}+1+dp_i) \]

解釋一下,\(\frac{i}{n}\)

表示湊巧按對了的期望,\(\frac{n-i}{n}\) 是按錯的概率,既然按錯了就需要按回來,需要按\(dp_{i+1}\)次,這次按了\(1\)鍵,需要加\(1\),注意,\(dp_i\)表示從最少需要按\(i\)個鍵轉移到最少需要按\(i-1\)個鍵的期望按鍵數量,那麼我們還需要轉移到最少需要按\(i-1\)個鍵,需要按\(dp_i\)

推一下式子,得出遞推式:

\[dp_i=\frac{n}{i}+\frac{n-i}{i}dp_{i+1} \]

顯然,我們要從\(cnt\)開始轉移,但是\(dp_{cnt}\)是啥啊?

要求出\(dp_{cnt}\),我們還需要前面的值,什麼時候值是可知的呢?

可以從\(dp_n=1\)開始轉移(需要按\(n\)個鍵,瞎按也\(100\%\)按到,我的程式碼裡用了這個)

也可以從\(dp_{n+1}=0\)開始轉移(感性理解,頂多按\(n\)鍵,不用按就少一鍵)

隨便挑吧!

這裡有個奇怪的問題,明明只用按\(cnt\)鍵,我們為什麼處理到了\(dp_n\)呢,它的意義何在?

其實\(dp_n\)是有意義的,因為本來只需要按\(cnt\)次,但是被\(B\)君隨機瞎按按壞啦,以至於退化到需要按\(n\)

現在就沒有什麼疑問了吧?

\(C++ Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define N 100005
#define p 100003
#define ll long long
using namespace std;
int n,k,cnt;
ll inv[N],dp[N];
bool close[N];
vector<int>g[N];
int main()
{
    scanf("%d%d",&n,&k);
    inv[1]=1;
    for (int i=2;i<=n;i++)
        inv[i]=(ll)(p-p/i)*inv[p%i]%p;
    for (int i=1;i<=n;i++)
        for (int j=i;j<=n/i;j++)
            if (j==i)
                g[i*j].push_back(i); else
                {
                    g[i*j].push_back(i);
                    g[i*j].push_back(j);
                }
    for (int i=1;i<=n;i++)
        scanf("%d",&close[i]);
    for (int i=n;i>=1;i--)
        if (close[i])
        {
            for (vector<int>::iterator it=g[i].begin();it!=g[i].end();++it)
                close[(*it)]=!close[(*it)];
            cnt++;
        }
    ll ans=k;
    if (cnt<=k)
        ans=cnt; else
        {
            dp[n]=1;
            for (int i=n-1;i>k;i--)
                dp[i]=(ll)n*inv[i]%p+(n-i)*inv[i]%p*dp[i+1]%p;
            for (int i=k+1;i<=cnt;i++)
                ans=(ans+dp[i])%p;
        }
    for (int i=1;i<=n;i++)
        ans=ans*(ll)i%p;
    ans=(ans%p+p)%p;
    printf("%lld\n",ans);
    return 0;
}