【比賽題解】CSP2020 提高組題解


T1. 儒略日

首先為了方便討論,先令 \(r \gets r + 1\),這樣的話,求的就是 " 第幾天 " 而不是 " 經過了幾天 " 了。

顯然可以考慮把 " 時間軸 " 分成億些 " 時間段 ",在每一段中根據 " 日期變化的周期性 " 計算答案。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int Q;

int c[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

int onelimit = 1721424;
int twolimit = 2299161;

bool isrn(long long x) {
	if (x % 4 != 0 || (x % 100 == 0 && x % 400 != 0)) return false;
	return true;
}

pair<int, int> turn(int x, bool rn) {
	int cnt = 0;
	for (int i = 1; i <= 12; i ++) {
		int delta = c[i];
		if (i == 2 && rn) delta ++;
		if (x <= cnt + delta) return make_pair(x - cnt, i);
		else cnt += delta;
	}
}

long long r;
int year;
long long cnt;

void work1() {
	if (r <= 366) {
		pair<int, int> ans = turn(r, 1);
		cout << ans.first << " " << ans.second << " " << 4713 << " BC" << endl;

		return;
	}

	r -= 366;

	cnt = (r - 1) / 1461 + 1;
	r -= (cnt - 1) * 1461;
	year = 4712 - (cnt - 1) * 4;

	cnt = (r - 1) / 365 + 1;
	if (r == 1461) cnt = 4;
	r -= (cnt - 1) * 365;
	year -= cnt - 1;

	pair<int, int> ans = turn(r, year % 4 == 1 ? 1 : 0);
	cout << ans.first << " " << ans.second << " " << year << " BC" << endl;
}

void work2() {
	r -= onelimit;

	cnt = (r - 1) / 1461 + 1;
	r -= (cnt - 1) * 1461;
	year = 1 + (cnt - 1) * 4;

	cnt = (r - 1) / 365 + 1;
	if (r == 1461) cnt = 4;
	r -= (cnt - 1) * 365;
	year += cnt - 1;

	pair<int, int> ans = turn(r, year % 4 == 0 ? 1 : 0);
	cout << ans.first << " " << ans.second << " " << year << endl;
}

void work3() {
	r -= twolimit;

	if (r <= 78) {
		if (r <= 17)
			cout << 14 + r << " " << 10 << " " << 1582 << endl;
		else if (r <= 47)
			cout << r - 17 << " " << 11 << " " << 1582 << endl;
		else
			cout << r - 47 << " " << 12 << " " << 1582 << endl;
		return;
	}

	r -= 78;

	if (r <= 731) {
		cnt = (r - 1) / 365 + 1;
		if (r == 731) cnt = 2;
		r -= (cnt - 1) * 365;
		year = 1582 + cnt;

		pair<int, int> ans = turn(r, cnt == 2 ? 1 : 0);
		cout << ans.first << " " << ans.second << " " << year << endl;

		return;
	}

	r -= 731;

	if (r <= 5844) {
		cnt = (r - 1) / 1461 + 1;
		r -= (cnt - 1) * 1461;
		year = 1585 + (cnt - 1) * 4;

		cnt = (r - 1) / 365 + 1;
		if (r == 1461) cnt = 4;
		r -= (cnt - 1) * 365;
		year += cnt - 1;

		pair<int, int> ans = turn(r, cnt == 4 ? 1 : 0);
		cout << ans.first << " " << ans.second << " " << year << endl;

		return;
	}

	r -= 5844;

	cnt = (r - 1) / 146097 + 1;
	r -= (cnt - 1) * 146097;
	year = 1601 + (cnt - 1) * 400;

	cnt = (r - 1) / 36524 + 1;
	if (r == 146097) cnt = 4;
	r -= (cnt - 1) * 36524;
	year += (cnt - 1) * 100;

	if (cnt == 4) {
		cnt = (r - 1) / 1461 + 1;
		if (r == 36525) cnt = 25;
		r -= (cnt - 1) * 1461;
		year += (cnt - 1) * 4;
	} else {
		cnt = (r - 1) / 1461 + 1;
		r -= (cnt - 1) * 1461;
		year += (cnt - 1) * 4;
	}

	cnt = (r - 1) / 365 + 1;
	if (r == 1461) cnt = 4;
	r -= (cnt - 1) * 365;
	year += cnt - 1;

	pair<int, int> ans = turn(r, isrn(year));
	cout << ans.first << " " << ans.second << " " << year << endl;
}

void work() {
	scanf("%lld", &r);
	r ++;

	if (r <= onelimit)
		work1();
	else if (r <= twolimit)
		work2();
	else
		work3();
}

int main() {
	scanf("%d", &Q);

	while (Q --)    work();

	return 0;
}

但是這個做法比較 naive,有沒有更給力點的?有沒有更加短小精悍的?

那當然還是有的:

  1. 對於 \(4713.1.1 \ \text{BC} \to 1600.12.31\) 的每一個日期,可以通過 " day by day " 的方式將答案先預處理出來。
  2. 對於 \(1601.1.1\) 及以后的每一個日期,可以通過 " 日期變化的周期性 " 來計算答案。

這樣子做會少討論許多的情況,比較好寫。

T2. 動物園

注意到對於所有的約束關系 \((p_i, q_i)\),所對應的 \(q_i\) 是互不相同的。

那么,也就是說,如果我這里有一個動物編號,其中的第 \(i\) 位存在約束關系,那我的第 \(i\) 位就會固定的影響到清單中的若干位。

約定變量:

  • \(\text{match}_j\):表示第 \(j\) 位是否存在約束關系。
  • \(\text{exist}_j\):表示是否存在一個 \(a_i\),滿足 \(a_i\) 的第 \(j\) 位為 \(1\)

那么當我們新加進去一個動物時,考慮每一位的取值。
顯然第 \(j\) 位填 \(0\) 是可以的,因為這樣不可能會影響到清單。
如果第 \(j\) 位填 \(1\),當且僅當滿足以下 \(2\) 個條件之一:

  1. \(\text{match}_j = 0\)

  2. \(\text{match}_j = 1 \land \text{exist}_j = 1\)

第一條顯然。
第二條指的是:雖然第 \(j\) 位存在約束關系,但是已經存在一個動物編號影響到了清單,那么我第 \(j\) 位填 \(1\) 也不會再次影響到清單了。

記 " 有多少位可以填 \(1\) " 的數量為 \(\text{fre}\),根據乘法原理,一共可以養 \(2^\text{fre}\) 個動物。

但是題目問的是 " 還可以養多少個 ",可以證明,養過的 \(n\) 個動物一定被包含在這 \(2^\text{fre}\) 之內。

故答案即為 \(2^\text{fre} - n\)

這題數據范圍比較刁鑽,有幾個 X 點:

  1. k == 64 && n == 0 && m == 0 :會爆 unsigned long long,需要特判,直接輸出 \(2^{64}\) 即可。
  2. fre == 64:使用 1ull << 64 時會爆炸,可以輸出 (1ull << 63) - n + (1ull << 63)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1001000;

int n, m, c, k;

unsigned long long a[N];

bool match[65];
bool exist[65];

int main() {
	scanf("%d%d%d%d", &n, &m, &c, &k);

	if (k == 64 && n == 0 && m == 0) {
		puts("18446744073709551616");
		return 0;
	}

	for (int i = 1; i <= n; i ++)
		scanf("%llu", &a[i]);

	for (int i = 1, p, q; i <= m; i ++) {
		scanf("%d%d", &p, &q);
		match[p] = 1;
	}

	for (int i = 1; i <= n; i ++)
		for (int j = 0; j < k; j ++)
			if (a[i] >> j & 1) exist[j] = 1;

	int fre = 0;
	for (int j = 0; j < k; j ++)
		if (!(match[j] && !exist[j])) fre ++;

	if (fre == 64) {
		unsigned long long ans = 0;
		ans += (1ull << 63);
		ans -= n;
		ans += (1ull << 63);
		printf("%llu\n", ans);
	} else {
		printf("%llu\n", (1ull << fre) - n);
	}

	return 0;
}

T3. 函數調用

不難看出,如果我把每個函數看成一個點。
那么對於每個 \(T_j = 3\) 的函數 \(j\),我讓 \(j\)\(g_1^{(j)}, g_2^{(j)}, ..., g_{C_j}^{(j)}\) 連邊的話,會得到一張 DAG。

記 " 調用函數 \(i\) 會使全局乘多少倍 " 為 \(\text{mul}_i\),該數組可以在 DAG 上記憶化搜索求出。

注意到影響到答案最后取值的操作只有 " 單點加 " 和 " 全局乘 " 兩種操作。
那么我可以嘗試把最終序列上,第 \(i\) 個位置上的數表示為下列該式的形式:

\[a_i \times b + k_i \]

其中:

  • \(a_i\) 表示:初始序列中第 \(i\) 個位置上的值。
  • \(b\) 表示:所有操作結束后," 全局乘 " 的倍數。
  • \(k_i\) 表示:所有 " 單點加 " 操作對第 \(i\) 個位置的貢獻。

那現在關鍵是在於如何求出每個 \(k_i\)

一個重要的思想是:如果我進行了一次 " 值為 \(a\) 的單點加 ",然后我進行了一次 " 值為 \(b\) 的全局乘 ",那么我可以看作是進行了 \(b\) 次 " 值為 \(a\) 的單點加 "。

記 " 函數 \(i\) 進行了多少次 " 為 \(f_i\),對於每次調用,先在節點上打上標記,最后再用拓撲排序向下傳遞貢獻。

具體地,倒序處理每一個調用(因為只有時間更靠后的 " 全局乘 " 才能影響到 " 單點加 "),假設說我這次要調用第 \(i\) 個函數,那么:

  • \(f_i \gets f_i + b\)
  • \(b \gets b \times \text{mul}_i\)

然后考慮拓撲排序,假設說我這次要處理第 \(i\) 個函數,那么:

  • \(T_i = 1\),則令 \(k_{P_i} \gets k_{P_i} + V_i \times f_i\)
  • \(T_i = 2\),則無視該操作。
  • \(T_i = 3\),則倒序處理每一個調用,假設說我現在要將貢獻傳遞給函數 \(j\),那么:
    • \(f_j \gets f_j + f_i\)
    • \(f_i \gets f_i \times \text{mul}_j\)

拓撲排序完直接輸出 \(a_i \times b + k_i\) 即可。

時間復雜度 \(\mathcal{O(n + m + Q)}\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

inline int read() {
	int x = 0, f = 1; char s = getchar();
	while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); }
	while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
	return x * f;
}

const int N = 100100;
const int mod = 998244353;

int n;

int a[N];

int m;

struct operation {
	int opt;
	int pos, val;
	vector<int> g;
} T[N];

int mul[N];

void calc(int u) {
	if (mul[u] != -1) return;

	switch (T[u].opt) {
		case 1: {
			mul[u] = 1;

			break;
		}

		case 2: {
			mul[u] = T[u].val;

			break;
		}

		case 3: {
			mul[u] = 1;
			for (int i = 0; i < (int)T[u].g.size(); i ++) {
				int v = T[u].g[i];
				calc(v);
				mul[u] = 1ll * mul[u] * mul[v] % mod;
			}

			break;
		}
	}
}

int Q;
int idx[N];

int b;
int f[N];

int deg[N];

int k[N];

void topsort() {
	queue<int> q;

	for (int i = 1; i <= m; i ++)
		if (deg[i] == 0) q.push(i);

	while (q.size()) {
		int u = q.front(); q.pop();

		switch (T[u].opt) {
			case 1: {
				k[T[u].pos] = (k[T[u].pos] + 1ll * f[u] * T[u].val) % mod; 

				break;
			}

			case 2: {
				break;
			}

			case 3: {
				for (int j = T[u].g.size() - 1; j >= 0; j --) {
					int v = T[u].g[j];
					f[v] = (f[v] + f[u]) % mod;
					f[u] = 1ll * f[u] * mul[v] % mod;
					if (-- deg[v] == 0) q.push(v);
				}

				break;
			}
		}
	}
}

int main() {
	n = read();

	for (int i = 1; i <= n; i ++)
		a[i] = read();

	m = read();

	for (int i = 1; i <= m; i ++) {
		T[i].opt = read();

		switch (T[i].opt) {
			case 1: {
				T[i].pos = read(), T[i].val = read();

				break;
			}

			case 2: {
				T[i].val = read();

				break;
			}

			case 3: {
				int C = read();
				for (int j = 1; j <= C; j ++) {
					int x = read();
					T[i].g.push_back(x);
				}

				break;
			}
		} 
	}

	memset(mul, -1, sizeof(mul));
	for (int i = 1; i <= m; i ++)
		if (mul[i] == -1) calc(i); 

	Q = read();

	for (int i = 1; i <= Q; i ++)
		idx[i] = read();

	b = 1;
	for (int i = Q; i >= 1; i --) {
		switch (T[idx[i]].opt) {
			case 1: {
				f[idx[i]] = (f[idx[i]] + b) % mod;

				break;
			}

			case 2: {
				b = 1ll * b * T[idx[i]].val % mod;

				break;
			}

			case 3: {
				f[idx[i]] = (f[idx[i]] + b) % mod;
				b = 1ll * b * mul[idx[i]] % mod;

				break;
			}
		}
	}

	for (int u = 1; u <= m; u ++) {
		if (T[u].opt != 3) continue;

		for (int j = 0; j < (int)T[u].g.size(); j ++) {
			int v = T[u].g[j];
			deg[v] ++;
		} 
	}

	topsort();

	for (int i = 1; i <= n; i ++)
		printf("%d ", (1ll * a[i] * b + k[i]) % mod);
	puts("");

	return 0;
}

T4. 貪吃蛇

算法一

特殊性質:\(n \leq 5 \times 10^4\)

先是一個引理 1:

引理 1

在出現最強蛇操作后成為最弱蛇的局面之前,捕食后產生的蛇的強度是單調遞減的。
即,捕食后產生的蛇中,先產生的蛇強於后產生的蛇。

簡單證明

在保證沒有出現最強蛇操作后成為最弱蛇的情況下。
顯然,當前的最強蛇沒有上次操作時的最強蛇強,當前的最弱蛇也沒有上次操作時的最弱蛇弱。根據不等式基本性質可知,當前產生的新蛇的實力值一定不大於上次操作時產生的蛇的實力值。

又由於給出來的實力值序列 \(\{a_n\}\) 滿足 \(a_1 \leq a_2 \leq \cdots \leq a_{n - 1} \leq a_{n}\)
那么我們將蛇分成 " 沒有被操作過 " 和 " 被操作過 " 兩類。
將第一類蛇取出來的順序肯定是按照 \(n, n - 1, \cdots\) 這樣編號遞減的順序取出來的。
由於將第一類蛇取出來后就會變成第二類蛇,所以第二類蛇產生的順序也是按照 \(n, n - 1, \cdots\) 這樣編號遞減的順序產生的,由於捕食后產生的蛇的實力值是單調遞減的,所以將第二類蛇取出來的順序自然也是按照 \(n, n - 1, \cdots\) 這樣編號遞減的順序取出來的。

所以,即使是在權值相等的情況下,當前產生的新蛇的編號也一定不大於上次操作時產生的蛇的編號。

故當前產生的新蛇的強度一定不大於上次操作時產生的蛇的強度。

然后有一個引理 2:

引理 2

在出現最強蛇操作后成為最弱蛇的局面之前,若一個最強蛇操作后不會成為最弱蛇,那么這條最強蛇一定會無腦進行該次操作。

簡單證明

設當前有一條最強蛇 \(A\)

(1):如果這個最強蛇 \(A\) 操作后還是最強蛇。那當然吃就完事了。

(2):如果這個最強蛇 \(A\) 操作后會有另一條蛇 \(B\) 取代了它最強的位置。由於我們假設的大前提,蛇 \(A\) 不會成為這次蛇 \(B\) 操作的目標。根據引理 1,蛇 \(B\) 進行該次操作后,強度一定不如操作過后的蛇 \(A\)。故蛇 \(B\) 成為狩獵對象的優先級一定高於蛇 \(A\)。在題目給定的 " 所有蛇均采取最優策略 " 的博弈背景下,蛇 \(B\) 會在不讓自己死的前提下采取最優策略,那么蛇 \(A\) 肯定也不會死。

綜上所述,引理 2 得證。

有了引理 1 和引理 2,可以考慮做一些事情。

注意到,不論蛇是怎么進行選擇,每一輪中的捕食者與被捕食者都是不會發生變化的。

根據引理 2,在出現最強蛇操作后成為最弱蛇的局面之前,所有的局面都可以無腦的進行下去。

那重點就在於,第一次出現最強蛇操作后成為最弱蛇的局面。
現在要討論該操作是否進行,就出現了蛇之間的千層博弈:

  • 當前有蛇 \(A, B\)

    • \(A\) 操作后成為了最弱蛇。
    • \(B\) 吃了蛇 \(A\) 后不會成為最弱蛇。

    考慮到蛇 \(A\) 進行了該次操作后會被蛇 \(B\) 無腦吃掉,那么蛇 \(A\) 不該進行這次操作。

  • 當前有蛇 \(A, B, C\)

    • \(A\) 操作后成為了最弱蛇。
    • \(B\) 吃了蛇 \(A\) 后成為了最弱蛇。
    • \(C\) 吃了蛇 \(B\) 后不會成為最弱蛇。

    考慮到蛇 \(B\) 吃了蛇 \(A\) 后自身難保,那么蛇 \(A\) 就會預判蛇 \(B\) 不敢吃它,那么蛇 \(A\) 就可以放心進行該次操作。

  • ......

亦可以歸納證明,當第一次出現最強蛇操作后成為最弱蛇的局面時,該次操作是否進行,與 " 最強蛇操作后成為最弱蛇 " 這樣的局面連續進行了多少次的奇偶性有關。若為偶數,那么該次操作可以放心進行;若為奇數,那么該次操作便不該進行。

討論到這里,本題就有了一個初步的做法,分成 Part 1 與 Part 2:

  • 在 Part 1 中:模擬操作,若第 \(i\) 步進行后最強蛇會成為最弱蛇,則進入 Part 2。

  • 在 Part 2 中:模擬操作,統計 " 最強蛇操作后成為最弱蛇 " 這樣的局面連續進行了多少次。
    若為偶數,則答案為 \(n - i\);若為奇數,則答案為 \(n - i + 1\)

使用線段樹或平衡樹隨意維護一下蛇,回答單次詢問的時間復雜度是 \(\mathcal{O}(n \log n)\) 的,期望得分 \(70\)

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

inline int read() {
	int x = 0, f = 1; char s = getchar();
	while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); }
	while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
	return x * f;
}

const int N = 1001000;

int T;

int n, m;
struct Node {
	int val;
	int id;
	Node() { val = id = 0; }
	Node(int X, int Y) : val(X), id(Y) {}
} a[N], zero = (Node) { 0, 0 };

bool operator < (Node a, Node b) {
	if (a.val != b.val) return a.val < b.val;
	return a.id < b.id;
}

bool operator == (Node a, Node b) {
	return a.id == b.id;
}

bool operator != (Node a, Node b) {
	return a.id != b.id;
}

bool operator > (Node a, Node b) {
	if (a.val != b.val) return a.val > b.val;
	return a.id > b.id;
}

void makedata(int id) {
	if (id == 1) {
		n = read();
		for (int i = 1; i <= n; i ++) {
			a[i].val = read();
			a[i].id = i;
		}
	} else {
		m = read();
		for (int i = 1; i <= m; i ++) {
			int x = read(), y = read();
			a[x].val = y;
		}
	}
}

struct SegmentTree {
	int l, r;
	Node max, min;
} t[N * 4];

void upd(int p) {
	t[p].max = zero;
	if (t[p * 2].max != zero && t[p * 2].max > t[p].max) t[p].max = t[p * 2].max;
	if (t[p * 2 + 1].max != zero && t[p * 2 + 1].max > t[p].max) t[p].max = t[p * 2 + 1].max;

	t[p].min = (Node) { 0x3f3f3f3f, n + 1 };
	if (t[p * 2].min != zero && t[p * 2].min < t[p].min) t[p].min = t[p * 2].min;
	if (t[p * 2 + 1].min != zero && t[p * 2 + 1].min < t[p].min) t[p].min = t[p * 2 + 1].min;
}

void build(int p, int l, int r) {
	t[p].l = l, t[p].r = r;
	if (l == r) {
		t[p].max = t[p].min = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(p * 2, l, mid), build(p * 2 + 1, mid + 1, r);
	upd(p);
}

void change(int p, int delta, Node val) {
	if (t[p].l == t[p].r) {
		t[p].max = t[p].min = val;
		return;
	}
	int mid = (t[p].l + t[p].r) >> 1;
	if (delta <= mid)
		change(p * 2, delta, val);
	else
		change(p * 2 + 1, delta, val);
	upd(p);
}

void work() {
	build(1, 1, n);

	int now = 1;
	for (; now < n; now ++) {
		if (now == n - 1) break;

		Node Max = t[1].max, Min = t[1].min;

		change(1, Min.id, zero);
		change(1, Max.id, zero);

		Node G = (Node) { Max.val - Min.val, Max.id };

		if (G < t[1].min) {
			change(1, Max.id, Max);
			change(1, Min.id, Min);
			break;
		} else {
			change(1, G.id, G);
		}
	}

	if (now == n - 1)  {
		puts("1");
		return;
	}

	int ans = n - now + 1;
	int round = 0;

	for (; now < n; now ++) {
		round ++;

		if (now == n - 1) {
			round ++;
			break;
		}

		Node Max = t[1].max, Min = t[1].min;

		change(1, Min.id, zero);
		change(1, Max.id, zero);

		Node G = (Node) { Max.val - Min.val, Max.id };

		if (G < t[1].min) {
			change(1, G.id, G);
		} else {
			round --;
			break;
		}
	}

	if (round % 2 == 0) ans --;

	printf("%d\n", ans);
}

int main() {
	T = read();

	for (int i = 1; i <= T; i ++) {
		makedata(i);
		work();
	}

	return 0;
}

算法二

特殊性質:\(1 \leq n \leq 10^6\)

從數據范圍中亦可猜得標算是一個線性的做法。
從輸入格式中亦可猜得標算充分利用了 \(\{a_n\}\) 的單調性,不然時間復雜度的瓶頸就會在排序上。

優化方式便是類似 「NOIP2016 提高組」蚯蚓 中的利用數據獨特的單調性,配合隊列來維護最值的方法。

具體的,開兩個隊列 \(\mathtt{Q_1}, \mathtt{Q_2}\)
其中 \(\mathtt{Q_1}\) 維護的是 " 沒有被操作過 " 的蛇。\(\mathtt{Q_2}\) 維護的是 " 被操作過 " 的蛇。
\(\mathtt{Q_1}, \mathtt{Q_2}\) 中的蛇均按強度遞減的順序存放。

根據定義,最強蛇便是 \(\mathtt{Q_1}, \mathtt{Q_2}\) 中隊首中的較大值,最弱蛇便是 \(\mathtt{Q_1}, \mathtt{Q_2}\) 中隊尾中的較小值。

相較於算法一中,不同的地方在於:

  • 在 Part 1 中:我們每次取出最強蛇與最弱蛇,然后進行該次操作,新產生的蛇直接放到 \(\mathtt{Q_2}\) 的隊尾即可,根據引理 1,不失單調性。

  • 在 Part 2 中:我們每次取出最強蛇與最弱蛇,先是判斷該次操作后最強蛇是否會成為最弱蛇,若最強蛇會成為最弱蛇,那么將新產生的蛇直接放到 \(\mathtt{Q_2}\) 的隊尾即可,根據最弱蛇的定義,不失單調性。

這樣就可以使用隊列來維護蛇了,回答單次詢問的時間復雜度是 \(\mathcal{O}(n)\) 的,十分優秀。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <deque>

using namespace std;

inline int read() {
	int x = 0, f = 1; char s = getchar();
	while (s < '0' || s > '9') {  s = getchar(); }
	while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
	return x * f;
}

const int N = 1000100;

int T;

int n, m;
struct Node {
	int val;
	int id;
	Node() { val = id = 0; }
	Node(int X, int Y) : val(X), id(Y) {}
} a[N];

bool operator < (Node a, Node b) {
	if (a.val != b.val) return a.val < b.val;
	return a.id < b.id;
}

bool operator == (Node a, Node b) {
	return a.id == b.id;
}

bool operator > (Node a, Node b) {
	if (a.val != b.val) return a.val > b.val;
	return a.id > b.id;
}

void makedata(int id) {
	if (id == 1) {
		n = read();
		for (int i = 1; i <= n; i ++) {
			a[i].val = read();
			a[i].id = i;
		}
	} else {
		m = read();
		for (int i = 1; i <= m; i ++) {
			int x = read(), y = read();
			a[x].val = y;
		}
	}
}

deque<Node> p, q;

int GetMax() {
	int id = 0; Node val = (Node) { -0x3f3f3f3f, 0 };
	if (p.size() && p.front() > val) id = 1, val = p.front();
	if (q.size() && q.front() > val) id = 2, val = q.front();
	return id;
}

int GetMin() {
	int id = 0; Node val = (Node) { 0x3f3f3f3f, n + 1 };
	if (p.size() && p.back() < val) id = 1, val = p.back();
	if (q.size() && q.back() < val) id = 2, val = q.back();
	return id;
}

void work() {
	while (p.size()) p.pop_back();
	while (q.size()) q.pop_back();

	for (int i = n; i >= 1; i --)
		p.push_back(a[i]);

	int now = 1;

	for (; now < n; now ++) {
		if (now == n - 1) break;

		int Max = GetMax(), Min = GetMin();
		Node X, Y;

		if (Max == 1) X = p.front();
		if (Max == 2) X = q.front();

		if (Min == 1) Y = p.back();
		if (Min == 2) Y = q.back();

		Node G;
		G.val = X.val - Y.val;
		G.id = X.id;

		if (X == Y) {
			if (Max == 1) p.pop_front();
			if (Max == 2) q.pop_front();
		} else {
			if (Max == 1) p.pop_front();
			if (Max == 2) q.pop_front();

			if (Min == 1) p.pop_back();
			if (Min == 2) q.pop_back();
		}

		int id = GetMin();
		Node cur;

		if (id == 1) cur = p.back();
		if (id == 2) cur = q.back();

		if (G < cur) {
			if (X == Y) {
				if (Max == 1) p.push_front(X);
				if (Max == 2) q.push_front(X);
			} else {
				if (Max == 1) p.push_front(X);
				if (Max == 2) q.push_front(X);

				if (Min == 1) p.push_back(Y);
				if (Min == 2) q.push_back(Y);
			}
			break;
		} else {
			q.push_back(G);
		}
	}

	if (now == n - 1) {
		puts("1");
		return;
	}

	int ans = n - now + 1;
	int round = 0;

	for (; now < n; now ++) {
		round ++;

		if (now == n - 1) {
			round ++;
			break;
		}

		int Max = GetMax(), Min = GetMin();
		Node X, Y;

		if (Max == 1) X = p.front();
		if (Max == 2) X = q.front();

		if (Min == 1) Y = p.back();
		if (Min == 2) Y = q.back();

		Node G;
		G.val = X.val - Y.val;
		G.id = X.id;

		if (X == Y) {
			if (Max == 1) p.pop_front();
			if (Max == 2) q.pop_front();
		} else {
			if (Max == 1) p.pop_front();
			if (Max == 2) q.pop_front();

			if (Min == 1) p.pop_back();
			if (Min == 2) q.pop_back();
		}

		int id = GetMin();
		Node cur;

		if (id == 1) cur = p.back();
		if (id == 2) cur = q.back();

		if (G < cur) {
			q.push_back(G);
		} else {
			round --;
			break;
		}
	}

	if (round % 2 == 0) ans --;

	printf("%d\n", ans);
}

int main() {
	T = read();

	for (int i = 1; i <= T; i ++) {
		makedata(i);
		work();
	}

	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM