4872: [Shoi2017]分手是祝願
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 372 Solved: 240
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
0 0 1 1
Sample Output
HINT
Source
這道題昨晚考試考了, 但是有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;
}