A
題意
有兩座塔由紅藍方塊組成,分別有 \(n,m\) 個方塊,一次操作可以把一座塔塔頂的方塊移動到另一座塔的塔頂,問透過操作是否能使每座塔中沒有顏色相同的相鄰方塊。
題解
知識點:貪心。
注意到,操作最多能拆掉一對相鄰的方塊,因此統計兩座塔不合法的對數。
- 如果超過 \(1\) 對,那麼無解。
- 如果只有 \(1\) 對,那麼操作一定使得塔頂相對,塔頂若顏色一樣就無解,否則有解。
- 如果沒有不合法的方塊,也有解。
時間複雜度 \(O(n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve() {
int n, m;
cin >> n >> m;
string s, t;
cin >> s >> t;
int mis = 0;
for (int i = 1;i < n;i++) if (s[i] == s[i - 1]) { mis++; }
for (int i = 1;i < m;i++) if (t[i] == t[i - 1]) { mis++; }
if (mis >= 2 || mis == 1 && s.back() == t.back()) return false;
else cout << "YES" << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "NO" << '\n';
}
return 0;
}
B
題意
座標軸上覆蓋了 \(n\) 個線段,指定一個點座標為 \(k\) ,能否透過刪除一些線段使得覆蓋指定點的線段嚴格最多。
題解
知識點:貪心。
直接去除沒有覆蓋 \(k\) 的線段,此時若 \(k\) 覆蓋數嚴格最多,那麼有解;否則,去除任意一條線段,都會使 \(k\) 和一些點同時減 \(1\) ,不會使得 \(k\) 覆蓋數嚴格最多,所以無解。
時間複雜度 \(O(n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve() {
int n, k;
cin >> n >> k;
vector<int> vis(57);
for (int i = 1;i <= n;i++) {
int l, r;
cin >> l >> r;
if (l <= k && k <= r) vis[l]++, vis[r + 1]--;
}
for (int i = 1;i <= 50;i++) vis[i] += vis[i - 1];
bool ok = 1;
for (int i = 1;i <= 50;i++) if (i != k) ok &= vis[k] > vis[i];
if (ok) cout << "YES" << '\n';
else return false;
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "NO" << '\n';
}
return 0;
}
C
題意
有 \(n\) 杯茶,第 \(i\) 杯茶有 \(a_i\) 毫升。有 \(n\) 個人,每個人每次最多能喝 \(b_i\) 毫升。
第一輪,第 \(i\) 個人喝第 \(i\) 杯茶,第 \(i\) 杯茶減少 \(\min(a_i,b_i)(1\leq i\leq n)\) 毫升。
第二輪,第 \(i\) 個人喝第 \(i-1\) 杯茶,第 \(1\) 個人不喝,第 \(i\) 杯茶減少 \(\min(a_i,b_{i+1})(1\leq i\leq n-1)\) 毫升。
以此類推,問最後每個人喝了多少毫升茶。
題解
知識點:列舉,字首和,差分,二分。
考慮列舉每杯茶的貢獻。第 \(i\) 杯茶會被 \([i,n]\) 內的人喝,但茶會被喝完,所以設 \(sumb\) 為 \(b\) 的字首和,找到最大的位置 \(j\) 滿足 \(sumb_j - sumb_{i-1}\leq a_i \iff sum_{j} \leq sum_{i-1} + a_i\) ,即 \([i,j]\) 的人能喝到自己的上限,第 \(j+1\) 個人能喝到剩下的 \(a_i - (sum_j - sum_{i-1})\) 。
我們用差分陣列 \(cnt\) 完成 \([i,j]\) 的區間加 \(1\) ,最後字首和就能得到第 \(i\) 個人能喝到自己的上限多少次。再用陣列 \(delta\) 記錄第 \(i\) 個人喝了多少剩下的茶,最後 \(cnt_ib_i + delta_i\) 即第 \(i\) 個人的毫升數。
時間複雜度 \(O(n\log n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int a[200007], b[200007];
ll sumb[200007];
int cnt[200007];
ll delta[200007];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i];
for (int i = 1;i <= n;i++) cin >> b[i];
for (int i = 1;i <= n;i++) sumb[i] = sumb[i - 1] + b[i], delta[i] = 0, cnt[i] = 0;
for (int i = 1;i <= n;i++) {
int id = upper_bound(sumb + i, sumb + n + 1, a[i] + sumb[i - 1]) - sumb - 1;
cnt[i]++;
cnt[id + 1]--;
delta[id + 1] += a[i] - (sumb[id] - sumb[i - 1]);
}
for (int i = 1;i <= n;i++) {
cnt[i] += cnt[i - 1];
cout << 1LL * cnt[i] * b[i] + delta[i] << ' ';
}
cout << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}
D
題意
給定一個有 \(n\) 個點 \(n\) 條邊的無向帶權圖,保證 \(n\) 為 \(6\) 的倍數,組成 \(\dfrac{n}{3}\) 個三元環: \((1,2,3),(4,5,6),\cdots\) 。
現在給每個點染上紅或藍兩種顏色,要求紅色有 \(\dfrac{n}{2}\) 個點、藍色也有 \(\dfrac{n}{2}\) 個點 。
定義一種染色方案的價值為,兩端顏色不同的邊的權值總和。
設所有染色方案種價值最大為 \(W\) ,問有多少種染色方案的價值為 \(W\) 。
題解
知識點:貪心,排列組合。
顯然,一個三元環的最多能貢獻兩條邊,即兩紅一藍或兩藍一紅,剛好我們可以使得所有三元環都能貢獻出兩條邊,我們對每個三元環貪心地選最大的兩條邊即可達到最大價值。因此,染色方案必然是每個三元環兩紅一藍或兩藍一紅,且最大的兩條邊端點顏色不同。
考慮三元環的分配方案,我們需要一半的兩紅一藍另一半兩藍一紅,因此方案數是 \(\dbinom{n/3}{n/6}\) 。再考慮每一種分配方案的不同染色方案,顯然三邊權值相同的三元組能貢獻三種方案,而兩邊權值相同的三元組當且僅當三邊中不同的權值是最大值時會貢獻兩種方案,分別記為 \(cnt_1,cnt_2\) ,則總方案數為 \(3^{cnt_1} \cdot 2^{cnt_2}\cdot \dbinom{n/3}{n/6}\) 。
時間複雜度 \(O(n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int P = 998244353;
const int N = 3e5 + 7;
namespace CNM {
int qpow(int a, ll k) {
int ans = 1;
while (k) {
if (k & 1) ans = 1LL * ans * a % P;
k >>= 1;
a = 1LL * a * a % P;
}
return ans;
}
int fact[N], invfact[N];
void init(int n) {
fact[0] = 1;
for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
invfact[n] = qpow(fact[n], P - 2);
for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
}
int C(int n, int m) {
if (n == m && m == -1) return 1; //* 隔板法特判
if (n < m || m < 0) return 0;
return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
}
}
int w[300007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
CNM::init(n / 3);
for (int i = 1;i <= n;i++) cin >> w[i];
int cnt1 = 0, cnt2 = 0;
for (int i = 3;i <= n;i += 3) {
if (w[i - 2] == w[i - 1] && w[i - 1] == w[i]) cnt1++;
else if (w[i] == w[i - 1] || w[i - 1] == w[i - 2] || w[i - 2] == w[i]) {
int mx = max({ w[i - 2],w[i - 1],w[i] });
if (count(w + i - 2, w + i + 1, mx) == 1) cnt2++;
}
}
cout << 1LL * CNM::qpow(3, cnt1) * CNM::qpow(2, cnt2) % P * CNM::C(n / 3, n / 6) % P << '\n';
return 0;
}
E
題意
有 \(n\) 個怪物,第 \(i\) 個怪物的血量有 \(h_i\) ,血量降為 \(0\) 時立刻死亡,你有兩種攻擊方式:
-
指定一個怪物,對其普通攻擊,造成 \(1\) 點傷害,消耗 \(1\) 點魔法。普通攻擊可以使用無限次。
-
指定一個怪物,對其釋放爆炸魔法,造成的傷害取決於注入的魔法,如果想要消耗 \(x\) 點魔法注入其中,會造成 \(x\) 點傷害。
爆炸魔法具有連鎖性,如果被魔法擊中的怪物死了,假設是第 \(i\) 個怪物,那麼 \(i-1,i+1\) 兩個怪物會受到 \(h_i-1\) 的傷害,以此類推,直至沒有怪物死掉。這個魔法只能使用一次。
問,最少需要多少魔法能消滅所有怪物。
題解
知識點:列舉,貪心,單調棧。
顯然,我們可以列舉釋放爆炸魔法的位置,取造成傷害最多的即可。
對於每個位置,都可以向兩側擴充套件,不妨先考慮左側部分。
我們設 \(l_i\) 為在第 \(i\) 個位置釋放爆炸魔法後,左側(包括自己)能造成的最大傷害。我們考慮在 \(i\) 處依次向左擴充套件的兩種情況:
- 對於 \(j<i\) ,若 \(h_j \geq h_i - (i-j)\) ,則說明 \(j\) 不能被消滅,但我們可以在之前透過普通攻擊把血量降到可以消滅的血量,因此還是可以造成 \(h_i-(i-j)\) 的傷害。
- 對於 \(j<i\) ,一旦出現 \(h_j < h_i-(i-j)\) ,則說明 \(j\) 雖然能被消滅,但會降低之後的魔法傷害,後續計算會由 \(h_j\) 直接支配,我們透過 \(l_i\) 直接轉移即可,並停止擴充套件。
注意到,如此操作的複雜度是平方的,我們考慮最佳化複雜度。我們可以將條件中的變數轉換為 \(h_j - j,h_i -i\) ,就可以繫結成一個值了。我們發現,滿足情況1造成的傷害都是連續減 \(1\) 的,而滿足情況 \(2\) 後直接加 \(f_j\) 後停止,所以我們只要求左側第一個滿足 \(h_j - j < h_i-i\) 的位置 \(j\) ,就可以直接得到造成的傷害 \(l_j + \dfrac{(i-j)(h_i-(i-j)+1 + h_i)}{2}\) 。
顯然,這個過程可以用單調遞增棧實現的,其可以維護一個字典序最小的極長遞增子序列(極長遞增子序列指,一個不能繼續遞增的遞增子序列),而子序列相鄰兩元素之間的在原陣列的其他元素,一定都大於等於這兩個元素,因此是不需要比較的。於是,對於一個值,我們比較維護的子序列,就可以跳過一些不需要比較的位置。整個過程,每個元素只會出入一次維護的子序列,因此複雜度是線性的。
對於右側,我們把 \(h\) 反轉,重新算一遍 \(h_i-i\) 做相同的事情得到 \(r_i\) ,再將 \(r_i\) 反轉,\(h\) 復原。最後,設血量總和為 \(sum\) ,列舉每個位置 \(i\) 得到 \(sum - (l_i + r_i - h_i) + h_i\) 取最小值。
時間複雜度 \(O(n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int n;
int h[300007], hi[300007];
ll l[300007], r[300007];
void calc(ll *f) {
stack<int> stk;
stk.push(0);
for (int i = 1;i <= n;i++) {
while (stk.size() > 1 && hi[stk.top()] >= hi[i]) stk.pop();
int len = min(h[i], i - stk.top());
f[i] = f[stk.top()] + 1LL * len * (h[i] + (h[i] - len + 1)) / 2;
stk.push(i);
}
}
bool solve() {
cin >> n;
ll sum = 0;
for (int i = 1;i <= n;i++) cin >> h[i], sum += h[i];
for (int i = 1;i <= n;i++) hi[i] = h[i] - i;
calc(l);
reverse(h + 1, h + n + 1);
for (int i = 1;i <= n;i++) hi[i] = h[i] - i;
calc(r);
reverse(h + 1, h + n + 1);
reverse(r + 1, r + n + 1);
ll ans = 1e18;
for (int i = 1;i <= n;i++) ans = min(ans, sum - (l[i] + r[i] - h[i]) + h[i]);
cout << ans << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}