二水中分白鷺洲
題目大意
假設水中 \(n\) 條體積相等的魚將按順序依次排列,準備進行戰鬥。初始時,每條魚可以選擇向左遊或向右遊;但是魚兒不太聰明,它們只會隨機選擇初始方向。
戰鬥時,若兩條不同方向的魚相遇,則體積大的魚會吃掉體積小的魚;如果兩條魚的體積相同,則向左遊的魚會吃掉向右遊的魚。吃完後,剩下的那條魚按照自己原本的方向繼續前行,同時體積變為原兩魚體積之和。可以認為吃魚的過程在瞬間完成。
無論體積的大小,魚兒們的速度始終保持一致。這樣戰鬥可能永遠不會結束,因此李白又劃定了戰鬥的邊界:如果魚兒到達邊界,則它必須立刻掉頭。當然,掉頭的過程也在瞬間完成。這樣直到只剩一條魚時,戰鬥結束。
解題思路
很有意思的一道題目。
性質
首先我們要發現一個性質。由於題目只說了順次排列,並不是等距排列,所以儘管魚以相同的速度移動,但時間發生的先後順序可能有所不同。
同樣的,這啟發我們,無論按照何種操作順序,最後的贏家總是相同的。
更嚴謹的,我們可以做以下思考。
魚相對位置的改變必然伴隨著吃與被吃事件的發生,故魚永遠是順序的。
由於魚的速度相同,所以相同方向的魚不可以互相吃,換而言之,只有相鄰且方向相反的魚才能發生吃與被吃的事件。
不存在因為操作順序不同而導致一條魚吃大了再把另一條沒吃夠的魚吃掉的情況。因為魚永遠是順序的,而一條魚只能吃掉同方向的魚,所以在另一條魚沒吃夠之前,兩條魚是不互相接觸的。
暴力
答案求 \(2^n * \sum i * p_i\)。
由於總共只有 \(2^n\) 中情況,記 \(i\) 贏的次數為 \(c_i\)。
答案即求 \(\sum i * c_i\)。
我們先考慮暴力,列舉每一條魚遊的方向,關鍵在於如何求出最後剩下哪一條魚。
上文論述了操作順序是無序的,我們梳理一下,一共有三種操作。
- 最左邊的調頭
- 最右邊的調頭
- 吃與被吃事件
我們先讓左右兩邊的調頭,顯然的是,接下來的方向序列中必有兩條魚相鄰且反向。
故我們可以找到第一條向左遊的魚。接著它會把它左邊的魚全部吃掉,然後調頭向右。
接著我們又可以找到第一條向左的魚(否則,由於最後一條必定向左,所以如果沒有向左的魚了,那麼最後也只剩一條魚了)。
它會將從第二條開始的魚全部吃掉。如果上一條魚的座標為 \(i\),這一條魚的座標為 \(j\),那麼這一條魚體積為 \(j-i\)。又上一條魚的體積為 \(i\)。
- 如果 \(j-i \geq i\) 即 \(j \geq 2*i\) 那麼這一條魚把上一條魚吃掉。
- 否則,上一條魚把這一條魚吃掉。
最後的體積都為 \(j\)。
一直重複上述過程,得到最後贏家即為:
最後一條 向左遊並滿足 \(j \geq 2*i\) 的魚。
\(i,j\) 定義同上,其中 最後一條 的要求是為了保證它不被後面的魚吃掉。
核心程式碼:
int lst=0,ans=0;
p[1]=1,p[n]=0;
for(int i=1;i<=n;i++){
if(p[i]==0){
if(i-lst>=lst) ans=i;
lst=i;
}
}
最佳化
考慮動態規劃,設 \(f(i)\) 表示滿足 \(i \geq 2*j\) 即 \(j \leq \lfloor \frac{i}{2} \rfloor\)(其中 \(j\) 為上一條向左遊的魚)的方案數。
容易得到轉移方程:
其中:
- \((j,i)\) 中全部向右,\([1,j)\) 隨意,所以權值 \(2^{j-1}\)。
- 第一條不可能向左,所以從 \(2\) 開始
- 加二是因為 \(i\) 可以作為第一條向左的魚,並且第一條初始時可以向左或向右,因為會先調頭向右,再開始操作。
推導一下,把外面的一提進去,運用等比數列求和公式:
注意到這裡求出的 \(f(i)\) 只滿足了一個條件,另一個條件 最後一條 並沒有被滿足。
故設 \(g(i)\) 表示只有 \(i\) 條魚時的答案。
其中若要從 \(g(j)\) 轉移過來(中間全部向右),需要保證 \(2*j > i\) 即 \(j > \lfloor \frac{i}{2} \rfloor\),使得 \(i\) 不滿足第一個條件,否則前面的答案就失效了(因為 \(i\) 一定滿足第二個條件)。
此時 \(i\) 必定滿足條件二,所以,記得加上自己的貢獻。
故有狀態轉移方程:
可以使用字首最佳化做到 \(O(n)\),二的冪可以直接遞推處理。
程式碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e7;
int n,mod;
int f[maxn+5],g[maxn+5];
int sm[maxn+5],pw[maxn+5];
int ans;
signed main(){
scanf("%lld%lld",&n,&mod);
pw[0]=1,pw[1]=2;
for(int i=1;i<=n;i++){
pw[i]=pw[i-1]*2%mod;
f[i]=pw[i/2];
g[i]=(sm[i-1]-sm[i/2]+f[i]*i)%mod;
sm[i]=(sm[i-1]+g[i])%mod;
}
ans=2*g[n]%mod;
printf("%lld",(ans+mod)%mod);
return 0;
}