Codeforces Global Round 13

DreamW1ngs發表於2021-03-01

比賽地址

A(水題)

題目連結

題目:
給出一個\(01\)序列,有2種操作:1.將某個位置取反;2.詢問\(01\)序列中第\(k\)大的數

解析:
顯然維護1的數目即可

#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

int ones = 0;
const int maxn = 1e5 + 5;
int dat[maxn];

int main() {
	int n, q, a, b;
	scanf("%d%d", &n, &q);
	for (int i = 0; i < n; ++i)
	{
		scanf("%d", &dat[i]);
		ones += dat[i];
	};
	while (q--) {
		scanf("%d%d", &a, &b);
		if (a == 1) {
			--b;
			if (dat[b])
				--ones;
			else
				++ones;
			dat[b] = 1 - dat[b];
		}
		else {
			printf("%d\n", ones >= b);
		}
	}
}

B(貪心)

題目連結
⭐⭐

題目:
給出一張圖,由\(n(n\le100)\)行(行從1開始編號),\(10^6+1\)列組成(列從0開始編號),在所給矩陣的每一行存在一個障礙,現在可以花費\(v\)使得障礙水平移動,\(u\)使得障礙豎直移動,問若要從\((1,0)\)可以到達\((n,10^6+1)\),最小花費是多少?
(題目所給障礙物水平移動範圍不包含兩端)

解析:
由於水平移動範圍不包含兩端,所以不會出現上下封閉的情況,那麼只有一種情況下無法到達,即所有障礙形成一條連續線段,將圖分割成左右兩部分,在這樣的情況下,分以下兩種情況進行討論:

  • 如果這是一條筆直的線段,即障礙所在列全部相同,則考慮將相鄰障礙物一個左移一個右移,或者將障礙物先水平移動再垂直移動到不同行,形成空缺
  • 如果不是筆直的,那麼在可以線上段斜線處水平移動一次或者豎直移動一次,形成空缺
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

int ones = 0;
const int maxn = 105;
int ob[maxn];

int main() {
	int T;
	int n, u, v;
	scanf("%d", &T);
	while (T--)
	{
		bool left = true;
		scanf("%d%d%d", &n, &u, &v);
		for (int i = 0; i < n; ++i)
			scanf("%d", &ob[i]);
		int last = 0;
		bool equ = true, line = true;
		for (int i = 1; i < n; ++i)
		{
			if (abs(ob[i] - ob[i - 1]) > 1)
			{
				line = false;
				break;
			}
			if (ob[i] != ob[i - 1])
				equ = false;
		}
		if (line) {
			if (equ)
				printf("%d", min(2 * v, u + v));
			else
				printf("%d", min(u, v));
		}
		else
			printf("%d", 0);
		printf("\n");
	}
}

C(思維)

題目連結
⭐⭐⭐

題目:
給出\(n\)個蹦床,每個蹦床有一個強度\(S_i\),如果身處\(i\)蹦床,會跳躍至\(i+S_i\)處,且每次蹦床被踩壓後,強度會\(-1\),但至多減至1,現在可以從任意位置起跳,每次跳躍直到無蹦床可以踩壓為止,問將所有蹦床強度降至\(1\),所最少需要的遊戲次數

解析:

  1. 假設從\(i\)蹦床出發,他只會對\(\ge i\)的部分產生影響,所以如果全要降為\(1\),則需要從前到後的遍歷蹦床,並將其強度減到\(1\)為止
  2. 這就要求維護一個\(t\)陣列,統計\(i\)之前的蹦床跳躍時對\(i\)的影響,顯然仍需要跳躍的次數為\(\max(S_i-1-t_i,0)\)
  3. \(i\)位置的強度降為\(1\),則會踩壓到\(i+2,i+3,...,i+S_i\)的所有蹦床,即所屬\(t\)均要加\(1\)
  4. 同時也不難發現,可能由於受之前影響的踩壓次數過多(未遍歷到\(i\)以前,\(S_i\)已經降為\(1\)了),就可以傳遞給下一個蹦床,即\(t[i+1]=\max(t_i-S_i+1,0)\)

注意:

  1. 答案需要開\(long\ long\)儲存
  2. \(i\)號蹦床對後序的影響,即\(+1\)過程,可以用差分陣列維護
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e3 + 5;
int dat[maxn];
int t[maxn];
long long ret;

int main() {
	int T,n;
	scanf("%d", &T);
	while (T--) {
		ret = 0;
		scanf("%d", &n);
		for (int i = 0; i < n; ++i)
			scanf("%d", &dat[i]);
		memset(t, 0, sizeof(t));
		for (int i = 0; i < n; ++i) {
			int x = max(0, dat[i] - 1 - t[i]);
			ret += x;
			int end = min(n - 1, i + dat[i]);
			for (int j = i + 2; j <= end; ++j)
				++t[j];
			t[i + 1] += max(t[i] - dat[i] + 1, 0);
		}
		printf("%lld\n", ret);
	}
}

D(思維+位運算)

題目連結
⭐⭐⭐

題目:
規定如果\(u\&v=v\),則\(u\)可達\(u+v\),現在給出\(u,v\),問是否可以從\(u\)\(v\)

解析:

  1. 如果\(u\rightarrow v\),則\(u<v\)是顯然的
  2. 不難發現對於任意一個\(u\),對於所有\(u\)可達點的中間變數\(v'\),滿足:\(v'\)中如果位數為1,則必有\(u\)中對應位也為\(1\),而\(u+v'\)可以使得\(1\)的位置向左移,如1101+0001=1110,且在\(1\)連續的情況下,可以選擇性的消除任意個多餘的\(1\),例如0111+0001=1000,或0111+0101=1100
  3. 那麼對於所給出的\(u\)\(v\)只要保證每個\(v\)的每一位的右邊位置,\(u\)\(1\)\(v\)多,則就可以通過不斷左移或消除\(1\),獲得所需要的\(v\)序列。換句話說即每個\(v\)中,位為\(1\)的右邊位上還含有若干個未被抵消(使用)的\(u\)的1

附:

  • \(+v'\)操作肯定不會使得\(u+v'>v\)原因在於,對於每個\(v\)如果有1,則他的右邊\(u\)存在很多1,這兩段二進位制程式碼的差一定是大於等於1的,如100000與0011010
  • 如果當前\(u\)位也存在1,則可以將這些多餘的1放在後續中被消除,這是一定能完成的。因為初始時保證\(u\le v\),如果二者\(1\)的數目不等,則必然存在\(v\)的一個最高位使得\(v\)中有\(1\),而\(u\)終沒有

E(暴力+分治+樹)

題目連結
⭐⭐⭐⭐⭐

題目:
規定\(Fib-tree\)必須滿足以下條件

  • 頂點數為\(Fibonacci數\)
  • 只有一個頂點或者可以通過消除某條邊將樹分割成兩個\(Fib-tree\)

現在給出樹的邊,問是否是一個\(Fib-tree\)

解析:

  1. 首先對頂點數進行判定是否為\(Fibonacci數\)

  2. 如若這個樹可以分割成兩個子\(Fib-tree\),則一定能肯定兩個子樹的頂點數為\(fib[k-1],fib[k-2]\),同時也可以證明,如果存在多條(最多兩條,由樹的定義可知)可以分割的邊,則消除任意一條可行邊不會影響結果
    證明:如果存在兩條邊分割子樹頂點數為\(fib[k-1],fib[k-2]\),如果使用某一條可行邊將其分割,另一條可行邊一定是在\(fib[k-1]\)對應的子樹中,會將\(fib[k-1]\)分割為\(fib[k-2],fib[k-3]\),所以兩條邊是等價的
    證明圖示

  3. 這樣的情況下,構建一個\(getSize\)函式獲取以某個點為根,子樹的大小,如果子樹大小為\(fib[k-1]\)或者\(fib[k-2]\)則考慮分割這條邊,然後進行遞迴性的處理即可

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 5;
vector<int> fib;
typedef pair<int, bool> P;
vector<P> e[maxn];
int siz[maxn];
int n;

void no() { printf("NO"); exit(0); }

void getSize(int u, int fa) {
	siz[u] = 1;
	for (auto& i : e[u]) {
		if (i.second || i.first == fa) continue;
		getSize(i.first, u);
		siz[u] += siz[i.first];
	}
}

void cutEdge(int u, int fa, int k, int& pu, int& pv, int& kd) {
	for (auto& i : e[u]) {
		if (pu) return;
		if (i.second || i.first == fa) continue;
		if (siz[i.first] == fib[k - 1] || siz[i.first] == fib[k - 2]) {
			pu = u, pv = i.first;
			kd = siz[i.first] == fib[k - 1] ? k - 1 : k - 2;
			return;
		}
		cutEdge(i.first, u, k, pu, pv, kd);
	}
}

void Check(int u, int k) {
	if (k <= 1) return;
	getSize(u, 0);
	int pu = 0, pv = 0, kd = 0;
	cutEdge(u, 0, k, pu, pv, kd);
	if (!pu) no();
	for (auto& i : e[pu])
		if (i.first == pv) i.second = true;
	for (auto& i : e[pv])
		if (i.first == pu) i.second = true;
	Check(pv, kd);
	Check(pu, 2 * k - 3 - kd);
}

int main() {
	int u, v;
	scanf("%d", &n);
	fib.push_back(1), fib.push_back(1);;
	while (fib.back() < n)
		fib.push_back(fib[fib.size() - 1] + fib[fib.size() - 2]);
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &u, &v);
		e[u].push_back(P(v, false));
		e[v].push_back(P(u, false));
	}
	if (fib.back() != n) no();
	Check(1, fib.size() - 1);
	printf("YES");
}

相關文章