比赛链接
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;
}
评论区