Luogu3750 [六省聯考2017]分手是祝願
阿新 • • 發佈:2020-08-07
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}\)
推一下式子,得出遞推式:
\[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; }