Codeforces Round #775 (Div. 2)
Contest Info
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
6 / 6 | O | O | O | O | Ø | Ø |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Reply
写完 发现忘记注册了,气得我怒开小号。顺便看看从 开始多少把能上紫。希望 不要不识好歹,少出点数论和几何。
EF会补的,等我会了再说。
F在补了
这两天影小姐的池子开了,玩了两天原神,给影小姐弄了一身破铜烂铁,F刚补完。米哈游你罪该万死!
感觉不是很会这个F,好难啊,瞎写了一点,提供一点思路吧QwQ,教学流也太难了。
Solutions
A. Game
题意:
从点 跳到点 , 点不可跳,相邻 点能直接走,最多只能跳一下,问最少跳多少步。
思路:
唯一的坑点就是只能跳一下,记录下首 和尾 就行。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
void solve() {
int n = rd();
vector<int> vec;
for (int i = 1; i <= n; ++i) {
int t = rd();
if (!t) vec.push_back(i);
}
int ans = 0;
if (vec.size() == 0) ans = 0;
else ans = vec[vec.size() - 1] - vec[0] + 2;
printf("%d\n", ans);
}
int main() {
int t = 1;
t = rd();
while (t--) solve();
return 0;
}
B. Game of Ball Passing
题意:
个人传球,告诉你每个人传球给别人的次数,问最少几个球让输入合法。
思路:
找到传球次数最多的人,看下其他人传球次数的总和。如果这个总和不小于最大值,这 个人就可以内部消耗,直到传球次数和最大值一致,就可以正好传完,只需要 个球。
其余情况则需要多个球了,因为最理想的情况就是每个人都传球给最多传球次数的人,剩下的就让他自己踢就行。
题目只问我们如何合法,所以我们不需要太关注传给谁,只关注传球的数量。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
void solve() {
int n = rd();
ll sum = 0, maxx = 0;
for (int i = 0; i < n; ++i) {
int t = rd();
if (t > maxx) maxx = t;
sum += t;
}
if (maxx == 0) {
puts("0");
return;
}
ll ans = max(1ll, maxx - (sum - maxx));
printf("%lld\n", ans);
}
int main() {
int t = 1;
t = rd();
while (t--) solve();
return 0;
}
C. Weird Sum
题意:
给你 的矩阵和每个点的颜色,求相同颜色之间所有无序对的曼哈顿距离和。
思路:
,我们可以发现曼哈顿距离中的 和 是可以分开来计算的。那么我们把所有相同颜色格子的 和 分开记录一下,从大到小排序一下,计算每个 () 对于距离和的贡献。
以 为例。设总共有 个格子,当前遍历到排序后的第 个 ,那么这个 在计算曼哈顿距离时,对所有的 (),都需要计算一次 。也就是说, 需要被加 次。同理,需要被前面的数减去 次。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
vector<int> X[100005], Y[100005];
void solve() {
int n = rd(), m = rd();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
int c = rd();
X[c].push_back(i);
Y[c].push_back(j);
}
}
ll ans = 0;
for (int i = 1; i <= 100000; ++i) sort(X[i].begin(), X[i].end(), greater<int>()), sort(Y[i].begin(), Y[i].end(), greater<int>());
for (int i = 1; i <= 100000; ++i) {
if (X[i].size() <= 1) continue;
for (int j = 0; j < X[i].size(); ++j) {
ans -= 1ll * j * X[i][j];
ans += 1ll * (X[i].size() - j - 1) * X[i][j];
ans -= 1ll * j * Y[i][j];
ans += 1ll * (Y[i].size() - j - 1) * Y[i][j];
}
}
printf("%lld\n", ans);
}
int main() {
int t = 1;
while (t--) solve();
return 0;
}
D. Integral Array
题意:
如果对于一个数组中的任意两个数 ,若 ,那么 也存在于这个数组中,这个数组就是好数组。
现在给你一个数组和所有数的取值上界 ,请你判断这个数组是否为好数组。
思路:
按照题意来写的话,枚举 和 ,虽然找一个数是否存在可以通过前缀和 找,但枚举的 就已经超时了。这时使用数论分块,找出所有的 ,再判断除数 的取值区间中有没有数字存在即可。但这样的时间复杂度是 的,也无法接受。
那么我们正难则反,先把 中所有不存在的值取出来(设为 数组),再和有的值(设为 数组)进行计算,看看是否存在 <, >,使得 中的数在数组 中存在。这一步就相当于枚举所有存在的 和不存在的 ,看看是否有非法的 存在。
因为上界 的存在,所以如果数组经过去重的操作之后,最坏情况下 ~ 都不存在,我们枚举的时候,及时 break
,枚举的次数就是 ,这就是一个调和级数了,所以时间复杂度就是 ,可以通过。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
void solve() {
int n = rd(), c = rd();
vector<int> a(n), pre(c + 1);
for (int i = 0; i < n; ++i) {
a[i] = rd();
}
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
int now = 1;
if (a[0] != 1) {
puts("No");
return;
}
vector<int> notHave;
for (auto v : a) {
pre[v]++;
while (now < v) {
now++;
if (now != v) {
notHave.push_back(now);
}
}
}
for (int i = 1; i <= c; ++i) pre[i] += pre[i - 1];
for (auto u : notHave) {
for (auto v : a) {
ll l = 1ll * u * v;
if (l > c) break;
ll r = min(1ll * (u + 1) * v - 1, (ll)c);
if (pre[r] - pre[l - 1] == 0) continue;
else {
puts("No");
return;
}
}
}
puts("Yes");
}
int main() {
int t = 1;
t = rd();
while (t--) solve();
return 0;
}
E. Tyler and Strings
题意:
给定序列 和 ,现对 进行重新排列,问能排列出多少种本质不同的序列 ,使得 的字典序小于 。
思路:
前置知识:设数字 出现的数量为 ,数字的最大值为 ,那么将这个序列排列成所有本质不同的序列的方案是
相当于对所有数字进行全排列,然后去掉每个数字各自的全排列对方案的重复影响。
要让字典序小,假设当前在第 位,那么 和 的前缀要一致,并且 ,后面就可以任意排列了。所以我们遍历一遍 ,查询 中有多少数字小于 ,挨个放上去,再计算 ,把这些贡献加入答案中,最后再把这一位填上 ,把相应的变量值更改一下,接着遍历下去就行。注意判断当前 中已经没有 的情况和 的情况。然后注意一下 可以排列成 的前缀串的情况。
但这样我们需要枚举比 小的数字,时间复杂度达到了 。我们再考虑遍历到 时产生的贡献,设当前的 为 ,那么产生的贡献就是 ,其实就相当于要维护一个数量的前缀和,用一个树状数组维护即可。
我们再预处理个逆元,时间复杂度就是 。
不知道为什么场上写不出来,感觉自己变笨了。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
ll f[200005], inv[200005];
ll powmod(ll a, ll b) {
ll res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
struct Bit {
int n;
vector<int> bit;
void init(int N) {
n = N;
bit.clear();
bit.resize(N + 1, 0);
}
int lowbit(int x) {
return x & -x;
}
void add(int i, int x) {
while (i <= n) {
bit[i] += x;
i += lowbit(i);
}
}
int query(int i) {
int ans = 0;
while (i) {
ans += bit[i];
i -= lowbit(i);
}
return ans;
}
}b;
void solve() {
int n = rd(), m = rd();
vector<int> s(n + 1), t(m + 1), cnt(200005);
b.init(200000);
for (int i = 1; i <= n; ++i) s[i] = rd(), b.add(s[i], 1), cnt[ s[i] ]++;
for (int i = 1; i <= m; ++i) t[i] = rd();
ll nowInv = 1;
for (int i = 1; i <= 200000; ++i) nowInv = nowInv * inv[ cnt[i] ] % mod;
ll ans = 0;
int exAdd = 1;
for (int i = 1; i <= n; ++i) {
if (i > m) break;
ans += f[n - i] * b.query(t[i] - 1) % mod * nowInv % mod;
if (cnt[ t[i] ] == 0) {
exAdd = 0;
break;
}
nowInv = nowInv * cnt[ t[i] ] % mod;
cnt[ t[i] ]--;
b.add(t[i], -1);
}
if (n >= m) exAdd = 0;
ans = (ans + exAdd) % mod;
printf("%lld\n", ans);
}
int main() {
f[0] = 1;
for (int i = 1; i <= 200000; ++i) f[i] = f[i - 1] * i % mod;
inv[200000] = powmod(f[200000], mod - 2);
for (int i = 200000; i >= 1; --i) inv[i - 1] = inv[i] * i % mod;
int t = 1;
while (t--) solve();
return 0;
}
F. Serious Business
题意:
给定 的矩阵,你要从点 走到 点 ,每次只能向下或者向右走,权值和为一路的点权和。一开始第二行所有点都不能走,你有 次操作,你可以选择执行其中若干次操作,使你拥有的权值减少 ,让第二行 成为可走点。问权值最大为多少。
思路:
理解一下题意,就是第一行连续走几步,然后向下走一步,再在第二行连续走几步,再向下走一步,最后走到底就行。假设第一行走到点 ,第二行走到点 ,那么权值花费就是
其中 代表将第二行的 区间内的各自都变为可走的最小花费。
一看数据范围,估计要遍历 的其中一维,然后快速地从决策集合中取出最优解。
首先点 必须都在操作区间内,这提示我们要遍历所有的操作区间,在操作区间内找到所有的合法转移,那么 应该代表一个最优前缀, 代表一个最优后缀,而且第一行和第三行的权值也确实是相应的前后缀,只剩下第二行的权值是一个区间权值和,而且对于转移有影响。那么区间权值和不就是前缀相减吗?
这里有个费用提前的 ,即在所有点 的状态中减去第二行的前缀和,在所有点 的前缀和中加上第二行的前缀和。这样前缀还是只有一个状态,并且我就可以直接在后缀中取出最大值,保证他是利用当前操作区间时的最优解。
我们发现前缀不受操作的影响,所以尝试从后往前遍历,进行转移。
假设当前遍历到点 ,我们再遍历所有以点 作为左区间的操作,如果点 在当前区间,那么求出最大值后,减去操作的花费 就是答案了。如果要进行转移,如何影响前面的操作区间呢?因为每行走的格子是连续的,所以点 必须能用到你转移过来的后缀才行,所以我们将区间最优后缀取出来,减去当前区间的花费 ,和之前计算出来的 的后缀取个最大值即可。
以上操作就可以用线段树来维护了。注意合并的时候要先用左区间的前缀和右区间的后缀合并,更新最优解,不然先对前缀后进行操作,可能造成左区间的后缀和右区间的前缀进行合并,进行非法转移。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e5 + 7;
ll f[2][N], a[3][N];
int n, q;
vector< pair<int, ll> > Q[N];
#define ls (id << 1)
#define rs (id << 1 | 1)
#define mid (l + r >> 1)
struct Seg {
struct Tree {
ll maxx, pre, suf;
} t[N << 2];
Tree merge(Tree a, Tree b) {
a.maxx = max({a.maxx, b.maxx, a.pre + b.suf});
a.pre = max(a.pre, b.pre);
a.suf = max(a.suf, b.suf);
return a;
}
void modify(int id, int l, int r, int p) {
if (l == r) {
t[id] = {f[0][l] + f[1][l], f[0][l], f[1][l]};
return;
}
if (p <= mid) modify(ls, l, mid, p);
else modify(rs, mid + 1, r, p);
t[id] = merge(t[ls], t[rs]);
}
Tree query(int id, int l, int r, int ql, int qr) {
if (r < ql || l > qr) return {-INF, -INF, -INF};
if (ql <= l && r <= qr) return t[id];
return merge(query(ls, l, mid, ql, qr), query(rs, mid + 1, r, ql, qr));
}
pair<ll, ll> getMax(int ql, int qr) {
Tree tmp = query(1, 1, n, ql, qr);
return {tmp.maxx, tmp.suf};
}
} seg;
#undef mid
void solve() {
n = rd(), q = rd();
for (int dd = 0; dd < 3; ++dd) {
for (int i = 1; i <= n; ++i) {
a[dd][i] = rd();
}
}
for (int i = 1; i <= n; ++i) {
f[0][i] = f[0][i - 1] + a[0][i] - a[1][i - 1];
f[1][i] = f[1][i - 1] + a[1][i];
}
ll tmp = 0;
for (int i = n; i >= 1; --i) {
tmp += a[2][i];
f[1][i] += tmp;
}
for (int i = 1; i <= q; ++i) {
int l = rd(), r = rd(), k = rd();
Q[l].push_back({r, k});
}
ll ans = -INF;
for (int ql = n; ql >= 1; --ql) {
seg.modify(1, 1, n, ql);
for (auto [qr, k] : Q[ql]) {
auto [maxx, maxSuf] = seg.getMax(ql, qr);
ans = max(ans, maxx - k);
f[1][ql - 1] = max(f[1][ql - 1], maxSuf - k);
}
}
printf("%lld\n", ans);
}
int main() {
int t = 1;
while (t--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2021-03-07 [2020 ICPC 上海 C] Sum of Log
2021-03-07 Codeforces Round #705 (Div. 2)