[CSP-S2020] 貪吃蛇
前言
喜歡思維題,更喜歡做不出來但看了題解直呼妙的思維題。
但是討厭考試時做不出來的思維題。/youl
題目
講解
這種題顯然要先挖掘性質。
- 性質一:如果一條蛇吃了之後不是最弱蛇,它一定吃。
證明的話稍微分類討論一下就好了:
- 如果最強蛇吃了之後還是最強蛇,不吃白不吃。
- 最強蛇吃了之後不是最強蛇,那麼次強蛇上位,次強蛇如果選擇不吃,安全;次強蛇如果吃,吃的一定是次弱蛇,吃了之後一定比最強蛇吃了最弱蛇還要弱,在之後的操作中如果次強蛇會被吃,那麼現在這次操作它一定會選擇不吃,依然安全。
現在問題就變成最強蛇吃了最弱蛇之後自己是最弱蛇的情況了。
為了方便,我們將所有蛇按實力動態排序為 \([1,n]\)
現在我們假設 \(n\) 吃了 \(1\),原來的 \(n\) 就變成了 \(1\) (動態更新排名),考慮 \(n-1\) 要不要吃 \(1\),如果 \(n-1\) 吃了 \(1\) 之後不是最弱蛇或者只剩一條蛇,它就會選擇吃,否則我們需要假設 \(n-1\) 吃 \(1\),然後看 \(n-2\) 的抉擇...
可以發現最後一定可以找到一條必吃的蛇,然後判斷它和最初始的 \(n\) 的奇偶性是否相同即可判斷最初的 \(n\) 是否選擇吃。
用 set 模擬這個過程可以做到 \(O(Tn\log_2n)\),\(70pts\) 到賬。
顯然正解應該是 \(O(Tn)\) 的,題目限制提示我們應該會用到單調性,由於第二個過程可以 \(O(n)\)
大力思考第一個過程中有什麼性質。
- 性質二:後吃的蛇一定比先吃的蛇弱。
現在我們用兩個雙端佇列儲存這些蛇,第一個存的是初始沒用過餐的蛇,第二個存的是用過餐的蛇,隊首弱,隊尾強。
我們每次要做的是從兩個佇列的隊尾取出一個最強的,從佇列一的隊首取出最弱的(由性質一+過程一可得出佇列二中一定不存在最弱的),吃掉之後與次弱比較,強於次弱則直接加入佇列二的隊首。
在性質一的證明 2. 中我們粗略說明了最強蛇和次強蛇都吃,次強蛇一定比最強蛇弱的結論。
所以這麼吃下去,用過餐的蛇一定也是單調的,但是這隻說明了佇列一中的蛇連續吃滿足性質二,有沒有可能最強蛇在佇列二中,且吃了之後比當前佇列二隊首強呢?
不可能。
我們考慮當前佇列二隊首 \(h_2\) 是怎麼進來的:
在 \(h_2\) 還沒進來的時候,它一定是佇列一隊尾,它和佇列二隊尾 \(t_2\) 比較之後發現自己更強,於是吃掉了那個時候的最弱蛇 \(s_1\) ,可以說明沒吃的時候 \(h_2\) 比 \(t_2\) 強。
在 \(h_2\) 進來之後,最弱蛇 \(s_1\) 已經由之前的次弱蛇 \(s_2\) 頂替,由 \(h_2\) 強於 \(t_2\),\(s_1\) 弱於 \(s_2\) 可知此時如果 \(t_2\) 吃掉這條 \(s_2\) ,一定比 \(h_2\) 吃掉 \(s_1\) 更弱,所以吃掉之後可以直接放到佇列二隊首,性質二完全正確!
在過程一結束之後只需要對這兩個佇列進行歸併排序即可做到 \(O(n)\)。
總時間複雜度 \(O(Tn)\)。
程式碼
其實沒有想象中那麼難打
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 1000005;
const int INF = 0x3f3f3f3f;
int n;
int a[MAXN];
LL Read()
{
LL x = 0,f = 1; char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
struct node
{
int val,ID;
bool operator < (const node &px)const{
if(val^px.val) return val < px.val;
return ID < px.ID;
}
node operator - (const node &C)const{
return node{val-C.val,ID};
}
}les[MAXN];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T = Read();
for(int cas = 1;cas <= T;++ cas)
{
if(cas == 1)
{
n = Read();
for(int i = 1;i <= n;++ i) a[i] = Read();
}
else
{
int k = Read();
for(int i = 1,pos;i <= k;++ i) pos = Read(),a[pos] = Read();
}
deque<node> q1,q2;
for(int i = 1;i <= n;++ i) q1.push_back(node{a[i],i});
int q1len,q2len;
while((q1len = q1.size()) + (q2len = q2.size()) > 2)//最小的一定是q1的頭,所以執行迴圈時q1永不空,
{
if(q2len && q1.back() < q2.back())//q2大!
{
node ne = q2.back() - q1.front(),se = node{INF,n+1};//new & second
if(q1len > 1 && q1[1] < se) se = q1[1];
if(q2len && q2[0] < se) se = q2[0];
if(se < ne) q2.push_front(ne),q1.pop_front(),q2.pop_back();
else break;
}
else//復讀機嘛
{
node ne = q1.back() - q1.front(),se = node{INF,n+1};//new & second
if(q1len > 1 && q1[1] < se) se = q1[1];
if(q2len && q2[0] < se) se = q2[0];
if(se < ne) q2.push_front(ne),q1.pop_front(),q1.pop_back();
else break;
}
}
if(q1len+q2len <= 2)
{
Put(1,'\n');
continue;
}
//歸併
int tot = 0;
while(!q1.empty() && !q2.empty())
if(q1.front() < q2.front()) les[++tot] = q1.front(),q1.pop_front();
else les[++tot] = q2.front(),q2.pop_front();
while(!q1.empty()) les[++tot] = q1.front(),q1.pop_front();
while(!q2.empty()) les[++tot] = q2.front(),q2.pop_front();
int ans = tot;
while(tot > 2)
{
if(les[tot] - les[1] < les[2]) les[1] = les[tot] - les[1],--tot;
else break;
}
if((tot & 1) == (ans & 1)) Put(ans-1,'\n');
else Put(ans,'\n');
}
return 0;
}