洛谷P7078 [CSP-S2020] 貪吃蛇 題解
比賽裡能做出這題的人真的非常厲害,至少他的智商和蛇一樣足夠聰明。
首先有一個結論:
當前最強的蛇吃了最弱的蛇之後,如果沒有變成最弱的蛇,他一定會選擇吃!
證明:
假設當前最強的蛇叫石老闆。
如果下一條最強的蛇如果依舊是石老闆,那肯定不吃白不吃;
如果下一條最強蛇不是石老闆,此時最強的蛇沒有原先強,最弱的蛇也沒原先弱,吃掉後肯定比石老闆要弱。也就是說,當前最強的蛇吃了之後,如果會死,也會死在石老闆前面。那麼這樣一來,這條蛇會想盡辦法不死,從而石老闆也一定能不死。
有了這個結論,一部分蛇可以放心大膽地吃了,但是問題來了,如果吃了之後變成最弱的蛇了,到底選擇吃不吃呢?
稍微往後推一推就明白了:
當前最強蛇記為石老闆,下一條最強蛇記為喵老闆。石老闆進食後變成最弱的蛇了,如果喵老闆進食後不是最弱的蛇,他就會選擇吃(根據開頭的結論),這樣石老闆就涼了,所以石老闆當初的選擇一定是不吃。
如果喵老闆進食後依舊是最弱的蛇,那就會考慮下一條最強蛇的情況,起名為汪老闆。同樣分兩種情況:如果汪老闆進食後不是最弱的蛇,那他就會選擇吃,這樣喵老闆就涼了,所以他當初會選擇不吃,這樣石老闆就不會死,那麼石老闆當初就會選擇吃。如果汪老闆進食後變成了最弱的蛇,那就再考慮下一條蛇………………
這個問題就變成了一個遞迴的問題了,直到某條蛇吃了之後不是最弱的蛇或者只能下兩條蛇為止。這樣,最後一條蛇會選擇吃,倒數第二條蛇為了保命會選擇不吃,倒數第三條蛇可以放心大膽的吃,倒數第四條蛇會保命選擇不吃,倒數第五條蛇可以放心吃………………
這樣,石老闆選擇吃不吃,就和最後一條蛇之間的奇偶性相關了。並且石老闆選擇不吃,遊戲結束,石老闆選擇吃,遊戲也會在下一輪結束(因為喵老闆會選擇不吃)。
到目前為止,這個題目很清晰了,只需模擬兩個階段即可:
階段一:所有最強蛇進食後都不是最弱蛇,放心大膽吃!
階段二:所有最強蛇進食後都是最弱蛇,直到有條蛇可以放心吃為止(吃了後不是最弱或者只剩兩條)
階段一結束時,遊戲就基本結束了(根據階段二的奇偶性看能不能再吃一次)
利用set可以方便地維護最強、最弱蛇,時間複雜度\(O(Tnlogn)\),只能拿 70 分,程式碼如下:
int a[N]; int main() { int _; scanf("%d", &_); int n; for (int cas = 1; cas <= _; cas++) { if (cas == 1) { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); } } else { int k; scanf("%d", &k); while (k--) { int x, y; scanf("%d%d", &x, &y); a[x] = y; } } set<pair<int, int> > se; for (int i = 1; i <= n; i++) { se.insert({a[i], i}); } int flag = 0, ans; while (1) { if (se.size() == 2) { se.erase(se.begin()); if (flag) { if ((flag - se.size()) % 2) { ans = flag + 1; } else { ans = flag; } } else ans = 1; break; } set<pair<int, int> >::iterator it = se.end(); it--; int x = it->first, id = it->second; int y = se.begin()->first; se.erase(it); se.erase(se.begin()); se.insert({x - y, id}); if (se.begin()->second != id) { if (flag) { if ((flag - se.size()) % 2) { ans = flag + 1; } else { ans = flag; } break; } } else { if (flag == 0) flag = se.size(); } } printf("%d\n", ans); } return 0; }
正解:用兩個雙端佇列\(q_1,q_2\)維護即可。
先把初始的有序蛇放進\(q_1\)裡,此時\(q_1\)已滿足單調性,頭部小,尾部大,我們後面會讓\(q_2\)也滿足這樣的單調性。
第一階段:
每次從\(q_1,q_2\)的尾部取出最強的蛇,從\(q_1\)頭部取出最弱的蛇,如果吃了以後是最弱的,那就進入第二階段,否則直接裝入\(q_2\)的頭部。
為什麼可以裝進\(q_2\)並且是\(q_2\)裡最弱的?其實證明最初的結論時已經解釋過了,後面進食的蛇肯定是越來越弱的,而且這個階段最弱的蛇一定在\(q_1\)中。
第二階段:
此時最弱的蛇,就沒必要丟進佇列裡了,單獨維護一下就好了,因為連續的一段進食後都是最弱的,直到總蛇數等於 22 或者進食後不是最弱為止,而最強的依舊從 \(q_1,q_2\)的尾部找。
這樣就不需要用其他帶\(log\)的資料結構維護了,時間複雜度\(O(Tn)\),程式碼如下:
int a[N];
int main() {
int _;
scanf("%d", &_);
int n;
for (int cas = 1; cas <= _; cas++) {
if (cas == 1) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
} else {
int k;
scanf("%d", &k);
while (k--) {
int x, y;
scanf("%d%d", &x, &y);
a[x] = y;
}
}
deque<pair<int, int> > q1, q2;
for (int i = 1; i <= n; i++) {
q1.push_back({a[i], i});
}
int ans;
while (1) {
if (q1.size() + q2.size() == 2) {
ans = 1;
break;
}
int x, id, y;
y = q1.front().first, q1.pop_front();
if (q2.empty() || !q1.empty() && q1.back() > q2.back()) {
x = q1.back().first, id = q1.back().second, q1.pop_back();
} else {
x = q2.back().first, id = q2.back().second, q2.pop_back();
}
pair<int, int> now = make_pair(x - y, id);
if (q1.empty() || q1.front() > now) {
ans = q1.size() + q2.size() + 2; // 不吃
int cnt = 0;
while (1) {
cnt++;
if (q1.size() + q2.size() + 1 == 2) {
if (cnt % 2 == 0) ans--;
break;
}
int x, id;
if (q2.empty() || !q1.empty() && q1.back() > q2.back()) {
x = q1.back().first, id = q1.back().second, q1.pop_back();
} else {
x = q2.back().first, id = q2.back().second, q2.pop_back();
}
now = {x - now.first, id};
if ((q1.empty() || now < q1.front()) && (q2.empty() || now < q2.front())) {
;
} else {
if (cnt % 2 == 0) ans--;
break;
}
}
break;
} else {
q2.push_front(now);
}
}
printf("%d\n", ans);
}
return 0;
}
轉自OMG_wc 的部落格