ABC219 覆盤

2020luke發表於2024-03-10

ABC219 覆盤

[ABC219A] AtCoder Quiz 2

思路解析

直接判斷 \(x\) 屬於的區間然後輸出即可。

時間複雜度:直接判斷,\(O(1)\)

code

#include<bits/stdc++.h>
using namespace std;
int x;
int main() {
	cin >> x;
	if(x < 40) cout << 40 - x;	//直接判斷即可
	else if(x >= 40 && x < 70) cout << 70 - x;
	else if(x >= 70 && x < 90) cout << 90 - x;
	else if(x >= 90) cout << "expert";
	return 0;
}

[ABC219B] Maritozzo

思路解析

將輸入的三個字串用陣列存下來,這樣拼接時就直接呼叫下標即可。

時間複雜度:需要遍歷 \(T\) 中的每個字元,\(O(\left\vert T \right\vert)\)

code

#include<bits/stdc++.h>
using namespace std;
string s[10], t;
int main() {
	cin >> s[1] >> s[2] >> s[3];
	cin >> t;
	for(int i = 0; i < t.size(); i++) {
		cout << s[t[i] - '0'];	//直接輸出對應下標的字串即可
	}
	return 0;
}

[ABC219C] Neo-lexicographic Ordering

思路解析

既然要將字母重新排列,那我們就可以直接將字串中對應字元給替換為給定的字母順序,然後按照編譯器自帶的運算子排序即可,記得最後要轉換回原來輸入的字元。

時間複雜度:首先需要將字串替換掉,還要進行一次排序,複雜度為 \(O(N\left\vert S \right\vert + N \log N)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n;
string x, s[N], t[N];
char z[130], c[130];
int main() {
	cin >> x;
	for(int i = 0; i < 26; i++) {
		z[x[i]] = 'a' + i;
		c['a' + i] = x[i];
	}
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> s[i];
		for(int j = 0; j < s[i].size(); j++) {
			s[i][j] = z[s[i][j]];	//直接將字串替換掉
		}
	}
	sort(s + 1, s + n + 1);	//排序
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j < s[i].size(); j++) {
			s[i][j] = c[s[i][j]];	//最後替換回來
		}
		cout << s[i] << '\n';
	}
	return 0;
}

[ABC219D] Strange Lunchbox

思路解析

可以發現 \(x,y \le 300\) 可用費用揹包做,費用就是每道菜有 \(1\) 的費用。注意如果全選價值最大能到 \(300 \times 300\),三維的 dp 記憶體會炸,於是我們發現答案只需要 \(\sum a_i \ge x,\sum b_i \ge y\),所以我們可以把大於等於 \(x\)\(y\) 的價值全部計算到 \(x,y\) 上,這樣值域就只有 \(300\),答案就是 \(f_{n,x,y}\)

注意如果不存在合法方案要輸出 -1

時間複雜度:三維 dp,每維分別是當前遍歷到了第幾道菜,價值 1 和價值 2,總複雜度 \(O(nxy)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 310;
int n, x, y, a[N], b[N], f[N][N][N];
int main() {
	cin >> n >> x >> y;
	for(int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
	}
	memset(f, 0x3f, sizeof(f));
	f[0][0][0] = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j <= x; j++) {
			for(int k = 0; k <= y; k++) {
				f[i][j][k] = min(f[i][j][k], f[i - 1][j][k]);
				int nx = min(j + a[i], x), ny = min(k + b[i], y);	//如果大於 x,y 就直接把答案記在 x,y 上
				f[i][nx][ny] = min(f[i][nx][ny], f[i - 1][j][k] + 1);
			}
		}
	}
	if(f[n][x][y] < 1e8) cout << f[n][x][y];
	else cout << -1;
	return 0;
}

[ABC219E] Moat

思路解析

一眼看到輸入資料只有 \(4\)\(4\) 列,直接想到狀壓列舉。可以直接列舉所有護城河所包含起來的格子,判斷是否連通以及判斷是否包含住了所有村莊。判斷連通我選擇用洪水填充,隨便選一個包含著的格子,若可以透過當前格移動到所有被包含格就說明連通。以及還要判斷被包圍格子是否形成了一個環,例如:

其中藍線表示護城河,綠色陰影表示護城河包圍的格子。可見圖中有兩條護城河,不符合題意。為排除掉這種情況,我們判斷每一個沒有被選擇的格子,判斷它是否被護城河完全包圍,也就是判斷能否走到地圖外即可。

時間複雜度:首先一次暴力列舉,然後對於每種情況需要判斷是否為可行方案,洪水填充最多遍歷 \(16\) 個格子,複雜度為 \(2^{16} \times 16\)

code

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int, int>
#define fir first
#define sec second
int v[8][8], flag[8][8], f[8][8], ans = 0;
bool vis[8][8];
int dx[4] = {0, 0, -1, 1};
int dy[4] = {-1, 1, 0, 0};
void flood(int x, int y) {
	queue< PII > q;
	q.push({x, y});
	while(!q.empty()) {
		int xx = q.front().fir, yy = q.front().sec;
		q.pop();
		for(int i = 0; i < 4; i++) {
			int nx = xx + dx[i], ny = yy + dy[i];
			if(flag[nx][ny] && !f[nx][ny]) {
				f[nx][ny] = true;
				q.push({nx, ny});
			}
		}
	}
}
bool flood_loop(int x, int y) {
	queue< PII > q;
	q.push({x, y});
	memset(vis, false, sizeof(vis));
	vis[x][y] = true;
	while(!q.empty()) {
		int xx = q.front().fir, yy = q.front().sec;
		q.pop();
		for(int i = 0; i < 4; i++) {
			int nx = xx + dx[i], ny = yy + dy[i];
			if(!flag[nx][ny] && !vis[nx][ny]) {
				if(nx >= 1 && nx <= 4 && ny >= 1 && ny <= 4) {
					vis[nx][ny] = true;
					q.push({nx, ny});
				}
				else return true;
			}
		}
	}
	return false;
}
int check() {
	int x = 0, y = 0, cnt = 0;
	for(int i = 1; i <= 4; i++) {
		for(int j = 1; j <= 4; j++) {
			if(flag[i][j]) cnt++, x = i, y = j;
		}
	}
	memset(f, 0, sizeof(f));
	f[x][y] = 1;
	flood(x, y);	//洪水填充判斷連通
	int sum = 0;
	for(int i = 1; i <= 4; i++) {
		for(int j = 1; j <= 4; j++) {
			if(f[i][j]) sum++;
		}
	}
	if(sum != cnt) return 0;	//不連通
	for(int i = 1; i <= 4; i++) {
		for(int j = 1; j <= 4; j++) {
			if(v[i][j] && !f[i][j]) return 0;	//若有村莊沒被包含
		}
	}
	for(int i = 1; i <= 4; i++) {
		for(int j = 1; j <= 4; j++) {
			if(!flag[i][j] && !flood_loop(i, j)) return 0;	//被護城河完全包圍
		}
	}
	return 1;
}
void dfs(int x, int y) {	//暴力列舉
	if(y > 4) y = 1, x++;
	if(x > 4) {
		ans += check();
		return;
	}
	flag[x][y] = 1;
	dfs(x, y + 1);
	flag[x][y] = 0;
	dfs(x, y + 1);
}
int main() {
	for(int i = 1; i <= 4; i++) {
		for(int j = 1; j <= 4; j++) {
			cin >> v[i][j];
		}
	}
	dfs(1, 1);
	cout << ans;
	return 0;
}

[ABC219F] Cleaning Robot

思路解析

要點:將整個圖拆分成每一輪的每一個點單獨考慮貢獻。

首先看到 \(k \le 10^{12}\) 發現不能直接列舉 \(k\) 輪,於是開始找每一輪的規律。首先可以知道,如果操作固定,那麼起點和路徑上每一個點以及終點的相對位置不會改變。也就是說每一輪的起點之間的相對位置,我們記作每一輪的偏移量,其實是不會改變的。若不理解,請看下圖。

其中紅線代表經過的位置和線路,黑線代表起點與各個點之間的偏移量。由此可見不論如何移動起點,它移動所形成的形狀是不會改變的。

瞭解完這個規律之後就很好理解了。我們可以先做出來第一次所經過的所有點的位置,由於起點和終點的偏移量不會改變的特點,我們在以後每一輪的這一個操作完後的位置,和上一輪進行完這一個操作之後的位置,偏移量也是不會改變的。若不理解,請看下圖。

其中紅點表示第一次經過的點,藍點表示第二次經過的點,黑線表示第一輪的當前次序遍歷到的點與第二輪的當前次序遍歷到的點之間的偏移量。可以發現每一個點的位置與下一輪的位置之間的偏移量也是不會變的,而這個偏移量其實就是每一輪的起點和終點的偏移量,因為每一輪的開始都是上一輪的結束,所以每一輪的起點的偏移量是固定的,那麼其他點的偏移量也是固定的。所以這時我們發現,有一些點在下一輪到達了未經過的位置,它對答案造成了貢獻(如上圖最右邊的兩個藍色點),而有一些則又經過了之前已經經過了的其他點,就對答案沒有貢獻(如上圖的其他藍色點)。

接下來我們則需要思考第一輪中有哪些點對答案有貢獻,貢獻分別是多少。我們考慮下面這幅圖。

紅,藍,綠點分別表示第一,二,三輪經過的點。我們發現後兩輪造成了貢獻的點只有三個,分別是 \((0,0),(0,2),(1,2)\) 這三點。而其中 \((0,0)\) 點只造成了 \(1\) 次貢獻,並且可以發現該點以後再也不會造成任何貢獻。原因是如果我們把一個點增加 \(1 \to k\) 次偏移量的每一種情況都列舉出來,如果中途的第 \(i\) 個點在第一輪就被遍歷過了,那麼第 \(i \to k\) 這些所有的輪數里當前點都不會再有任何貢獻。換句話說,當前點對答案的貢獻就是 \(i\),因為還有第一輪造成的貢獻。

明確了答案統計的方式之後,我們需要計算的值就是對於第一次移動遍歷到的每一個點,找到加上 \(i\) 輪偏移量後會與第一輪經過的某一個點重合的最小的 \(i\)。由於每一輪之間的偏移量不變,所以當前點經過 \(k\) 輪移動後所有的點都在同一條斜率為 \(\frac{x}{y}\) 的直線上,其中 \(x,y\) 分別是偏移量的 \(x,y\) 軸。所以問題就變成了對於每一個點,找到經過當前點的斜率為 \(\frac{x}{y}\) 的直線上往後找與當前點最接近的點,計算當前點需要經過多少輪的偏移量才能到達這個點,也就是將距離除以偏移量的值。這裡的點指第一輪中經過的所有點。這樣我們就可以把每一條直線給找出來,對於每一條直線都用一個 vector 存下這條直線上所有的點與起點之間經過的輪數,設 \(a,b\) 分別為當前點的 \(x,y\) 軸,起點就是 \((a \mod x,b-y\times\lfloor a/x \rfloor)\)。最後的答案就是對於每一條直線,設 \(v_{i}\) 為當前直線上的第 \(i\) 個點,求 \(\sum_{i=1}^{size-1} \min(v_{i+1}-v_{i},k)+k\),因為如果相差輪數大於 \(k\),那麼對答案就沒有影響,同時最後一個點不會被阻擋,所以要加上 \(k\)

最後就是幾個需要注意的小細節:

  • \(x=0,y=0\),就說明之後的每一輪都不會有新的貢獻,直接輸出第一輪的貢獻即可。
  • \(x=0,y \ne 0\),為了防止 \(a \mod x\) 沒有結果,我們交換 \(x,y\) 和所有的 \(a,b\)
  • \(x<0\),為方便計算,我們取反 \(x\) 和所有的 \(a\)

code

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int, int>
#define fir first
#define sec second
string str;
long long k;
vector< PII > v;
int dx[150], dy[150];
void init() {
	dx['U'] = -1;
	dx['D'] = 1;
	dy['L'] = -1;
	dy['R'] = 1;
}
signed main() {
	init();
	cin >> str >> k;
	int x = 0, y = 0;
	v.push_back({x, y});
	for(int i = 0; i < (int)str.size(); i++) {
		x += dx[(int)str[i]], y += dy[(int)str[i]];
		v.push_back({x, y});
	}
	sort(v.begin(), v.end());
	v.erase(unique(v.begin(), v.end()), v.end());
	if(x == 0 && y == 0) {
		cout << v.size(); return 0;
	}
	if(x == 0) {
		swap(x, y);
		for(auto &it : v) swap(it.fir, it.sec);
	}
	if(x < 0) {
		x = -x;
		for(auto &it : v) it.fir = -it.fir;
	}
	map< PII, vector<int> > mp;
	for(auto it : v) {
		int nx = it.fir, ny = it.sec;
		int mod = (nx % x + x) % x;
		mp[{mod, ny - (long long)y * (nx - mod) / x}].push_back((nx - mod) / x);
	}
	long long ans = 0;
	for(auto it : mp) {
		sort(it.sec.begin(), it.sec.end());
		for(int i = 0; i <= (int)it.sec.size() - 2; i++) {
			ans += min(k, (long long)it.sec[i + 1] - it.sec[i]);
		}
		ans += k;
	}
	cout << ans;
	return 0;
}

相關文章