EDU-CFR-143解题报告
A. Two Towers
题意:有两个积木塔,由红色/蓝色积木组成,你每次可以将一个塔的塔顶放到另一个塔上,问任意次操作后能否使得两座塔都是红蓝相间。
可以将一个塔全部转移到另一个塔上,那么操作相当于把“大塔”从中间分开两半。如果“大塔”中红红/蓝蓝的数量超过
By tourist
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int tt;
cin >> tt;
while (tt--) {
int n, m;
cin >> n >> m;
string a;
string b;
cin >> a >> b;
reverse(b.begin(), b.end());
a += b;
int cnt = 0;
for (int i = 0; i < n + m - 1; i++) {
cnt += (a[i] == a[i + 1]);
}
cout << (cnt <= 1 ? "YES" : "NO") << '\n';
}
return 0;
}
B. Ideal Point
题意:数轴上有若干条线段,问能否删去若干条,使
成为被覆盖最多的点(严格最多)。
容易发现,只要有两个线段,一个左端点在
By tourist
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int tt;
cin >> tt;
while (tt--) {
int n, k;
cin >> n >> k;
vector<int> l(n), r(n);
for (int i = 0; i < n; i++) {
cin >> l[i] >> r[i];
}
bool L = false;
bool R = false;
for (int i = 0; i < n; i++) {
L |= (l[i] == k);
R |= (r[i] == k);
}
cout << (L && R ? "YES" : "NO") << '\n';
}
return 0;
}
C. Tea Tasting
题意:有
杯茶和 个人,第 杯茶有 单位的茶水,第 个人对每杯茶最多和 单位。初始第 个人站在第 杯茶前,每人喝一次,然后 个人往左平移一位(第一个人结束),继续重复上面步骤。问每个人最后分别喝到了多少茶。
我们可以从左到右扫每杯茶,看这杯茶对后面的人造成的贡献。首先可以预处理前缀和,二分查找这杯茶到第几个人被喝完,这样,这个人之前的人每人都能“喝饱”;最后一个人喝掉剩下的茶。区间修改可以在差分数组上改两位,最后求前缀和即可。
By jiangly
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n), b(n);
for (int i = 0; i < n; i++)
std::cin >> a[i];
for (int i = 0; i < n; i++)
std::cin >> b[i];
std::vector<i64> s(n + 1);
for (int i = 0; i < n; i++)
s[i + 1] = s[i] + b[i];
std::vector<int> d(n);
std::vector<i64> ans(n);
for (int i = 0; i < n; i++) {
int j = std::lower_bound(s.begin() + i, s.end(), a[i] + s[i]) - s.begin() - 1;
if (j > i) {
d[i]++;
if (j < n)
d[j]--;
}
if (j < n)
ans[j] += a[i] - (s[j] - s[i]);
}
for (int i = 1; i < n; i++)
d[i] += d[i - 1];
for (int i = 0; i < n; i++) {
ans[i] += 1LL * d[i] * b[i];
std::cout << ans[i] << " \n"[i == n - 1];
}
}
D. Triangle Coloring
题意:有
个点,其中 分别形成三角形。边有边权。你需要将 个点染成黑色, 个点染成白色,定义一种方案的权值为,所有两端异色的边的权值和。求让权值和最大化的方案数,对 取模。
题目的大体理解是要尽可能多的异色边,所以可以先考虑最多情况。容易发现,一个三角形内最多只能有两个异色边,且让每个三角形都有两个是可以做到的(在
对于三角形内的方案数,如果较小的两条边不同,那么只有一种方案;如果三条边相同,则有三种方案;如果较小的两条边相同,较大的不同,则有两种。
由于要在
By tourist
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
Mint ans = 1;
for (int i = 0; i < n; i += 3) {
vector<int> a(3);
for (int j = 0; j < 3; j++) {
cin >> a[j];
}
sort(a.begin(), a.end());
if (a[0] < a[1]) {
ans *= 1;
}
if (a[0] == a[1] && a[1] < a[2]) {
ans *= 2;
}
if (a[0] == a[1] && a[1] == a[2]) {
ans *= 3;
}
}
for (int i = n / 6 + 1; i <= n / 3; i++) {
ans *= i;
}
for (int i = 1; i <= n / 6; i++) {
ans /= i;
}
cout << ans << '\n';
return 0;
}
E. Explosions?
题意:有
个怪物,第 个有 滴血,每次普通攻击花费 能量,可以让一个怪物减 滴血。在若干次普通攻击后,可以发一个大招,消耗 能量,选择某个怪物(设为 )减 滴血,如果杀死,则相邻怪物减 滴血;如果继续杀死相邻怪物,则有传递性(如杀死 , 怪物会减 滴血)。你必须通过这一个大招杀掉所有剩下的怪物。问总的最小能量消耗。
显然要让普通攻击尽可能少,且把阵型优化成能够连续杀完的形态。可以考虑 DP:记
以左边为例,
但是,我们发现,对于一个连续段,要减少只能统一减少,所以可以维护当前的连续段信息。具体地,维护一个栈,元素形如
当加入一个怪物时,可以新建一个连续段
细节上,需要注意血量到
By cxm1024
int a[300010], f[2][300010];
void Solve(int test) {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
struct node {int l, r, lx, rx;};
for (int tt = 0; tt <= 1; tt++) {
stack<node> st;
int now = 0;
for (int i = 1; i <= n; i++) {
st.push({i, i, a[i], a[i]});
while (st.size() > 1) {
node tmp = st.top(); st.pop();
if (tmp.lx <= st.top().rx) {
now += (st.top().rx - tmp.lx + 1) * (st.top().r - st.top().l + 1);
st.top().lx -= st.top().rx - tmp.lx + 1, st.top().r = tmp.r, st.top().rx = tmp.rx;
}
else {st.push(tmp);break;}
}
if (st.top().lx < 0) {
now -= (-st.top().lx) * (-st.top().lx + 1) / 2;
st.top().l += (-st.top().lx), st.top().lx = 0;
}
f[tt][i] = now;
}
reverse(a + 1, a + n + 1);
}
reverse(f[1] + 1, f[1] + n + 1);
int ans = 1e18;
for (int i = 1; i <= n; i++)
ans = min(ans, a[i] + f[0][i] + f[1][i]);
cout << ans << endl;
}
F. Blocking Chips
题意:有一颗树,有若干个点为黑色且放了棋子,其他点为白色。你可以依次操作每个棋子,将它移动一步到白色点,并将该点染成黑色。操作完最后一个棋子后返回第一个棋子循环操作。问最多操作次数。
容易发现,棋子走后就无法回头,所以相当于对于每个棋子,选出一条以该棋子为端点的路径。
这种最大化的问题,看上去就非常像二分答案。考虑二分操作次数,那么前面若干个棋子会走
略微思考一下性质,容易发现一个棋子要尽可能往子树内走,这样能给上面的棋子留下更多空间。于是可以考虑 DP:
那么,对于一个有棋子的点,假设需要走
对于一个没有棋子的点,考虑所有儿子。如果有两个儿子需要往上走,就不合法;如果有一个儿子需要往上走,条件允许的话可以“拐个弯”再往下走(即通过别的子树的空地,内部解决问题,不需要往上走)。否则就必须要往上走。
看起来很多情况,但其实很多都可以统一写法:使用
By jiangly
void solve() {
int n;
std::cin >> n;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
u--, v--;
adj[u].push_back(v);
adj[v].push_back(u);
}
int k;
std::cin >> k;
std::vector<int> a(k);
for (int i = 0; i < k; i++) {
std::cin >> a[i];
a[i]--;
}
int lo = 0, hi = n;
while (lo < hi) {
int x = (lo + hi + 1) / 2;
std::vector<int> f(n, -1);
for (int i = 0; i < k; i++) {
f[a[i]] = x / k + (i < x % k);
}
std::vector<int> dp(n);
bool ok = true;
auto dfs = [&](auto self, int x, int p) -> void {
int neg = -f[x], max = 0;
for (auto y : adj[x]) {
if (y == p) {
continue;
}
self(self, y, x);
if (dp[y] < 0) {
if (neg <= 0) {
ok = false;
}
neg = dp[y] + 1;
} else {
max = std::max(max, dp[y]);
}
}
if (neg <= 0) {
if (neg + max >= 0) {
dp[x] = 0;
} else {
dp[x] = neg;
}
} else {
dp[x] = max + 1;
}
// std::cerr << "dp[" << x << "] = " << dp[x] << "\n";
};
dfs(dfs, 0, -1);
// std::cerr << "x = " << x << "\n";
if (ok && dp[0] >= 0) {
lo = x;
} else {
hi = x - 1;
}
}
std::cout << lo << "\n";
}
这种统一写法的方式非常值得学习,我赛时的代码就是直接分类讨论情况,又难写又不好调试。写代码之前要三思而后行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步