ABC221 覆盤

2020luke發表於2024-04-04

ABC221 覆盤

[ABC221A] Seismic magnitude scales

思路解析

資料範圍 \(B \le A \le 10\),可以發現能直接暴力求解。注意開 long long

code

//ABC221A
#include<bits/stdc++.h>
using namespace std;
int a, b;
int main() {
	cin >> a >> b;
	a -= b;
	long long ans = 1;
	for(int i = 1; i <= a; i++) {
		ans *= 32;
	}
	cout << ans;
	return 0;
}

[ABC221B] typo

思路解析

直接按照題意遍歷 \(s\) 的每個下標與下一個下標交換,然後判斷是否相同即可。注意可以不進行任何操作,要多一次判斷。

時間複雜度:需要遍歷每個下標\(O(\left\vert S \right\vert)\),而每次交換後的判斷也需要 \(O(\left\vert S \right\vert)\),因此總複雜度為 \(O(\left\vert S \right\vert ^2)\)

code

//ABC221B
#include<bits/stdc++.h>
using namespace std;
string s, t;
int main() {
	cin >> s >> t;
	if(s == t) {
		cout << "Yes";
		return 0;
	}
	for(int i = 1; i < s.size(); i++) {
		swap(s[i], s[i - 1]);
		if(s == t) {
			cout << "Yes";
			return 0;
		}
		swap(s[i], s[i - 1]);
	}
	cout << "No";
	return 0;
}

[ABC221C] Select Mul

思路解析

可以想到想讓乘積最大盡量讓位數更平均,而同時我們也需要讓更大的位數排在更前面,於是我們可以先將原數存成陣列從大到小排個序,然後貪心取即可。

時間複雜度:設 \(x=\log_{10}N+1\),有一層排序,因此複雜度為 \(O(x \log x)\)

code

//ABC221C
#include<bits/stdc++.h>
using namespace std;
int n;
int main() {
	cin >> n;
	vector<int> v;
	while(n > 0) {
		v.push_back(n % 10);
		n /= 10;
	}
	sort(v.begin(), v.end(), [](int x, int y) {
		return x > y;
	});
	int x = 0, y = 0;
	for(int i = 0; i < v.size(); i++) {
		if(x <= y) x = x * 10 + v[i];
		else y = y * 10 + v[i];
	}
	cout << x * y;
	return 0;
}

[ABC221D] Online games

思路解析

首先可以想到先用差分打標記,最後結束時在統計即可。但可惜資料範圍 \(A_i \le 10^9\),不能存下來每一個時間點,於是想到離散化。將每個區間離散成 \(2\) 個點存下來,這樣總點數就只有 \(2 \times N\) 個,可以透過。最後拿區間之間的人數乘上區間長度即可。

時間複雜度:一次排序加去重,複雜度為 \(O(N \log N)\)

code

//ABC221D
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, a[N], b[N], c[5 * N], ans[N];
int main() {
	cin >> n;
	vector<int> v;
	for(int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
		v.push_back(a[i]);
		v.push_back(a[i] + b[i]);
	}
	v.push_back(n + 1);
	sort(v.begin(), v.end());
	v.erase(unique(v.begin(), v.end()), v.end());
	for(int i = 1; i <= n; i++) {
		int pa = lower_bound(v.begin(), v.end(), a[i]) - v.begin() + 1;
		int pb = lower_bound(v.begin(), v.end(), a[i] + b[i]) - v.begin() + 1;
		c[pa]++; c[pb]--;
	}
	for(int i = 1; i < v.size(); i++) {
		c[i] += c[i - 1];
		ans[c[i]] += v[i] - v[i - 1];
	}
	for(int i = 1; i <= n; i++) {
		cout << ans[i] << ' ';
	}
	return 0;
}

[ABC221E] LEQ

思路解析

很有思維量的一道題。首先根據題目要求發現,新求的子序列只跟子序列的頭尾有關,而在確定頭尾之後中間的元素選或不選沒有任何關係。也就是確定新子序列的頭尾下標分別為 \(i,j\),那麼以當前頭尾的可行子序列個數就是 \(2^{j-i-1}=2^j \div 2^{i+1}\) 種可能。

接下來我們思考,根據題目要求,上方的 \(i,j\) 一定有 \(a_i \le a_j\),於是我們要做的其實就是找固定 \(j\),有多少個 \(i\) 使得 \(i<j,a_i \le a_j\),也就是個二維偏序。但事實上不需要那麼複雜,假設有 \(i_1,i_2,\dots,i_k\) 都滿足題目的條件,那麼以 \(j\) 結尾的子序列的個數就是 \(2^j \div 2^{i_1+1}+2^j \div 2^{i_2+1}+\dots+2^j \div 2^{i_k+1}=2^j \div (2^{i_1+1}+2^{i_2+1}+\dots+2^{i_k+1})\)

那麼接下來就很輕鬆了,由於上方的 \(i,j\) 一定有 \(a_i \le a_j\),所以我們可以用一個 \(rk_i\) 存下 \(a_i\) 的排名,設 \(v_{rk_i}=2^{i+1}\),然後求 \(\sum_{k=1}^{rk_j-1}v_k\),這樣我們就可以求得滿足 \(a_i \le a_j\) 的條件所有的 \(i\) 對答案的貢獻,還要繼續考慮 \(i<j\) 這個條件。我們其實可以不先處理出來 \(v\) 的值,而是在 \(j \to j+1\) 時處理 \(v_j\) 的值,這樣就能保證所有 \(v\) 當中有值的 \(v_{rk_i}\) 都有 \(i<j\)。由於 \(v\) 需要滿足單點修改和區間求和的操作,所以需要使用樹狀陣列最佳化。

注意:由於有取模和乘方,所以需要使用逆元和快速冪求解。

時間複雜度:需要遍歷每個下標,都需要一次區間查詢和單點修改,複雜度為 \(O(n \log n)\)

code

//ABC211E
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 3e5 + 10;
const ll mod = 998244353;
int n, m;
ll a[N], c[N], b[N];
map<ll, int> rk;

ll ksm(ll a, ll b, ll p) {
	ll ans = 1;
	while(b) {
		if(b & 1) {
			ans = ans * a % p;
		}
		a = a * a % p;
		b >>= 1;
	}
	return ans;
}
ll niyuan(ll a, ll p) {
	return ksm(a, p - 2, p);
}

void add(ll x, ll y) {
	for(; x <= n; x += (x & -x)) {
		c[x] = (c[x] + y) % mod;
	}
}
ll ask(ll x) {
	ll sum = 0;
	for(; x > 0; x -= (x & -x)) {
		sum = (sum + c[x]) % mod;
	}
	return sum;
}

int main() {
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		b[i] = a[i];
	}
	sort(b + 1, b + n + 1);
	for(int i = 1; i <= n; i++) {
		rk[b[i]] = i;
	}
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ans = (ans + (ksm(2, i, mod) * ask(rk[a[i]])) % mod) % mod;
		add(rk[a[i]], ksm(niyuan(2, mod), i + 1, mod));
	}
	cout << ans;
	return 0;
}

[ABC221G] Jumping sequence

思路解析

題意很好理解,四向移動,步長確定,可見移動方式非常複雜,\(x,y\) 軸分別有三種可能的偏移量,於是我們選擇將整個座標系順時針旋轉 \(45^\circ\),這樣偏移量就只有加減兩種可能,同時終點也變成了 \((x-y,x+y)\)。如此題目就變成了一道可行性揹包問題,可以用 \(f_{i,j}\) 表示走了 \(i\) 步,第 \(j\) 行/列能否到達,由於每次行和列的偏移量的值相同,因此可以存在一個陣列內。最後就是用 bitset 最佳化然後判斷一下能否到達並根據 dp 過程中的值輸出路徑即可。

時間複雜度:\(O(N)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 2010, M = 3.6e6 + 10;
int n, a, b, d[N], x, y, ans[N];
bitset<M> f[N];
int main() {
	cin >> n >> a >> b;
	x = a - b, y = a + b;
	int sum = 0;
	for(int i = 1; i <= n; i++) {
		cin >> d[i];
		sum += d[i];
	}
	if(sum < abs(x) || sum < abs(y)) {cout << "No"; return 0;}
	if((x + sum) % 2 == 1 || (y + sum) % 2 == 1) {cout << "No"; return 0;}
	x = (x + sum) / 2, y = (y + sum) / 2;
	f[0][0] = true;
	for(int i = 1; i <= n; i++) {
		f[i] = f[i - 1] | (f[i - 1] << d[i]);
	}
	if(!f[n][x] || !f[n][y]) {cout << "No"; return 0;}
	cout << "Yes\n";
	for(int i = n; i >= 1; i--) {
		if(f[i - 1][x] && f[i - 1][y]) ans[i] = 0;
		else if(!f[i - 1][x] && f[i - 1][y]) ans[i] = 1;
		else if(f[i - 1][x] && !f[i - 1][y]) ans[i] = 2;
		else if(!f[i - 1][x] && !f[i - 1][y]) ans[i] = 3;
		if(ans[i] & 1) x -= d[i];
		if(ans[i] & 2) y -= d[i];
	}
	for(int i = 1; i <= n; i++) {
		if(ans[i] == 0) cout << "L";
		else if(ans[i] == 1) cout << "D";
		else if(ans[i] == 2) cout << "U";
		else if(ans[i] == 3) cout << "R";
	}
	return 0;
}