[BZOJ]4872 [SHOI2017] 分手是祝願 期望DP

4872: [Shoi2017]分手是祝願

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 372  Solved: 240
[Submit][Status][Discuss]

Description

Zeit und Raum trennen dich und mich.
時空將你我分開。B 君在玩一個遊戲,這個遊戲由 n 個燈和 n 個開關組成,給定這 n 個燈的初始狀態,下標爲
從 1 到 n 的正整數。每個燈有兩個狀態亮和滅,我們用 1 來表示這個燈是亮的,用 0 表示這個燈是滅的,遊戲
的目標是使所有燈都滅掉。但是當操作第 i 個開關時,所有編號爲 i 的約數(包括 1 和 i)的燈的狀態都會被
改變,即從亮變成滅,或者是從滅變成亮。B 君發現這個遊戲很難,於是想到了這樣的一個策略,每次等概率隨機
操作一個開關,直到所有燈都滅掉。這個策略需要的操作次數很多, B 君想到這樣的一個優化。如果當前局面,
可以通過操作小於等於 k 個開關使所有燈都滅掉,那麼他將不再隨機,直接選擇操作次數最小的操作方法(這個
策略顯然小於等於 k 步)操作這些開關。B 君想知道按照這個策略(也就是先隨機操作,最後小於等於 k 步,使
用操作次數最小的操作方法)的操作次數的期望。這個期望可能很大,但是 B 君發現這個期望乘以 n 的階乘一定
是整數,所以他只需要知道這個整數對 100003 取模之後的結果。

Input

第一行兩個整數 n, k。
接下來一行 n 個整數,每個整數是 0 或者 1,其中第 i 個整數表示第 i 個燈的初始情況。
1 ≤ n ≤ 100000, 0 ≤ k ≤ n;

Output

輸出一行,爲操作次數的期望乘以 n 的階乘對 100003 取模之後的結果。

Sample Input

4 0
0 0 1 1

Sample Output

512

HINT

Source

[Submit][Status][Discuss]


HOME Back

  這道題昨晚考試考了, 但是有80分的水分是跟期望半毛錢關係沒有的, 就是n==k的情況. 但是實際上這是一個啓示. 看到期望就怕啊... 所以昨晚考試看都沒看, 更別說部分分的提示了, 去A t3去了. 現在知道怎麼做了, 大概腦子裏模擬了一下如果我能考場上做出這道題是如何想到這個解題思路的.

  首先這道題看起來很難. 因爲每次都是隨機關, 而且還翻轉約數, 而且到某個範圍還停止計算, 看起來就感覺是一道很神的題目. 那麼一時半會兒想不出結果我們可以考慮拿點部分分. 首先n==k的部分分很顯眼? 那麼有沒有<= n的做法. 每次翻轉約數, 都是比自己小的, 那就從大的倒着考慮. 發現顯然倒着關一定能在n步之內關完. 而且倒着關一定是最優的. 簡單證明一下: 很明顯先關一個的燈再關一個比他序號大的燈和先關那個大的燈再關這個燈是等價的. 那麼一切都可以轉化成倒着關。 又因爲當前最大亮着的燈只能由自己關, 那麼一路倒推回去一定保證是最優的了. 考場上就算沒有證明其實也能感性理解這樣"貪心"是正確的.

  那麼部分分到手. 再看如何AC此題. 題目中給出的某個比較顯眼的條件就是<=k就直接操作... 其實這很像遞歸裏的邊界? 那麼k是什麼屬性, 我們可以考慮建個與k屬性相同的dp方程, 這樣就可以在同屬性下操作, 就有邊界條件. 比如說這道題裏面k是操作次數(屬性), 那麼我們可以建一個關於操作次數的方程, 這樣就擁有了一個邊界條件可以很好的利用. 並且這道題是隨機關燈, 那麼dp肯定不是表示前i個燈怎麼樣怎麼樣...  那麼用操作次數來建就更加可信了. .那麼這樣該怎麼設dp方程呢? 轉移又是怎樣?

  考慮部分分給我們的提示. 最優解一定是倒着關的步數, 同時我們進一步思考最優解實際上就是正確的選擇, 每一步都不走錯的選擇. 那麼隨機關會造成什麼樣的影響呢? 首先開關燈滿足異或關係, 那麼假設最優解是要關某i個燈, 先關哪個都無所謂. 那麼假設隨機關了這i個燈之一, 那麼我們需要完成的正確選擇的次數就減一了, 代表我們按正確了.   那如果沒按正確呢? 很明顯我們需要完成的正確選擇次數+1. 爲什麼是1呢? 這代表了你得把按掉的燈按回來一次. 爲什麼不能由一個比你大的把你按滅? 但是那樣就又做了一個錯誤的抉擇, 因爲那個大的只能是錯誤的選擇, 如果按掉的那個是正確的那說明你這次選擇不是錯誤的而是正確的(說明你沒影響啊). 我們由部分分給我們的提示發現可以按正確操作次數來dp, dp[i]表示還剩i次正確操作的期望操作次數. 很明顯dp[k] = k, 這就是邊界條件. 特殊的dp[n+1] = 0.

  那麼轉移就是dp[i] = i / n * dp[i - 1] + (1 - i /n) * dp[i + 1]+1 . 實際上這個時候就已經可以做了, 但略複雜:wys的題解.

  但是遇到f[i-1]和f[i+1]可以考慮用差分來做. dp[i]表示從i次正確選擇到i-1次正確選擇的操作數. 轉移就是dp[i] = i / n + (1 - i / n) * (1 + b[i + 1] + b[i]). 這樣就方便多了, 化簡一下就是dp[i] = ((n - i) * b[i+1] + n) / i. 那麼k~num的dp[i]之和就是答案.

  這道題給出的一個思考方式就是題目中的某些邊界條件可以加以利用來靠近dp方程的設計, 並且要學會利用部分分的提示.

#include<bits/stdc++.h>
using namespace std;
typedef long long lnt;
const int mod = 1e5 + 3;
const int maxn = 1e5 + 5;
int n, k, num, a[maxn];
lnt inv[maxn], b[maxn], ans;
int main() {
	inv[1] = 1;
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	for (int i = 2; i <= n; ++ i)
		inv[i] = (-(mod / i) * inv[mod % i] % mod + mod) % mod;
	for (int i = n; i; -- i)
		if (a[i]) {
			++ num;
			for (int j = 1; j * j <= i; ++ j)
				if (i % j == 0) {
					a[j] ^= 1;
					if (j * j != i) a[i / j] ^= 1;
				}
		}
	for (int i = n; i; -- i) b[i] = ((n - i) * b[i + 1] % mod + n) * inv[i] % mod;
	if (k >= num) ans = num;
	else {
		b[k] = k;
		for (int i = k; i <= num; ++ i)
			ans = (ans + b[i]) % mod;
	}
	for (int i = 2; i <= n; ++ i)
		ans = ans * i % mod;
	printf("%lld\n", ans);
	return 0;
} 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章