1. 程式人生 > 其它 >[CSP-S2020] 貪吃蛇

[CSP-S2020] 貪吃蛇

如果我能挖掘出性質,但僅僅是如果。

前言

喜歡思維題,更喜歡做不出來但看了題解直呼妙的思維題。

但是討厭考試時做不出來的思維題。/youl

題目

洛谷

講解

這種題顯然要先挖掘性質。

  • 性質一:如果一條蛇吃了之後不是最弱蛇,它一定吃。

證明的話稍微分類討論一下就好了:

  1. 如果最強蛇吃了之後還是最強蛇,不吃白不吃。
  2. 最強蛇吃了之後不是最強蛇,那麼次強蛇上位,次強蛇如果選擇不吃,安全;次強蛇如果吃,吃的一定是次弱蛇,吃了之後一定比最強蛇吃了最弱蛇還要弱,在之後的操作中如果次強蛇會被吃,那麼現在這次操作它一定會選擇不吃,依然安全。

現在問題就變成最強蛇吃了最弱蛇之後自己是最弱蛇的情況了。

為了方便,我們將所有蛇按實力動態排序為 \([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;
}