64道CodeForces 1600 - 1700的题目
插个眼,等刷够64道题以后,看看是个什么水平
(做题进度:40/64,复盘进度:17/64)
目前从中学到的思想 / 方法 / 技巧有:
- 1600无难题,只要想学,就一定能学会
- 先把思路想明白了再去写代码!无论花多长时间去推思路都无所谓,只要想明白了,写代码就是分分钟的事情,贸然下手只能喜提WA2
补:推完了再写!不要感觉推的差不多了就开始写,要完全推出来再去写 - 贪心的思路想不通一定要试试dp,不要觉得dp是什么很高深的东西,dp更多的是一种思路、思维、思想,很多时候用dp思想考虑问题反而会变简单
补:dp的思路想不通也要试试贪心!不要盲目dp,并非所有问题都是dp都会更好分析,如果某个问题和贡献有关,那么很可能是贪心的思路 - 根据时间复杂度分析问题,O(n) O(nlogn) O(n^2),根据复杂度的需求去猜测和设计解法
- 在面对很多“求最小”的问题,不只是贪心,也有可能是二分枚举最优解(二分答案)
- 在面对一些区间问题的时候,可能要用到前缀和的思想,比如ans = cal(r) - cal(l - 1)这样
或者是dp的思路,比如f[i]比f[i - 1]多出来[1, i][2, i] ... [i, i]共i个区间这样 - 容斥原理,详见https://www.cnblogs.com/WelCo/p/19003709
- 正确的解法一般不会有很多特判,如果写了一堆特判要看看是不是自己思路想错了
本人的板子
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define debug(x) std::cout << #x << " = " << x << '\n'
#define debugp(x, y) std::cout << #x << ", " << #y << " = " << "[" << x << ", " << y << "]\n"
#define debugt(x, y, z) std::cout << #x << ", " << #y << ", " << #z << " = " << "[" << x << ", " << y << ", " << z << "]\n"
#define debug1(f, a, b) std::cout << #f << ": "; for (int i = (a); i <= (b); i++) std::cout << (f)[i] << " \n"[i == (b)]
#define debug2(f, a, b, c, d) std::cout << #f << ":\n"; for (int i = (a); i <= (b); i++) for (int j = (c); j <= (d); j++) std::cout << (f)[i][j] << " \n"[j == (d)]
#define debug_1(q) std::cout << #q << ": "; for (auto it : (q)) std::cout << it << ' '; std::cout << '\n'
#define debug_2(q) std::cout << #q << ":\n"; for (auto [x, y] : (q)) std::cout << '[' << x << ", " << y << "]\n"
#define debug_3(q) std::cout << #q << ":\n"; for (auto [x, y, z] : (q)) std::cout << '[' << x << ", " << y << ", " << z << "]\n"
#define show_pq(q) std::cout << #q << ": "; while ((q).size()) { std::cout << (q).top() << ' '; (q).pop(); } std::cout << '\n'
#define show_gh(g) std::cout << #g << ":\n"; for (int i = 1; i <= n; i++) { std::cout << i << ": "; for (auto it : (g)[i]) std::cout << it << ' '; std::cout << '\n'; }
using i64 = int64_t;
using pii = std::pair<int, int>;
using namespace __gnu_pbds;
using ordered_set = tree<i64, null_type, std::less<i64>, rb_tree_tag, tree_order_statistics_node_update>;
using ordered_multiset = tree<i64, null_type, std::less_equal<i64>, rb_tree_tag, tree_order_statistics_node_update>;
constexpr int N = 2e5 + 10;
constexpr int inf = INT_MAX;
constexpr i64 INF = LLONG_MAX;
constexpr int mod = 998244353;
constexpr int dx[] = {+0, -1, +0, +1, -1, +1, +1, -1};
constexpr int dy[] = {+1, +0, -1, +0, +1, +1, -1, -1};
/*====================My_Solution====================//
为了防止还未推出正确思路就贸然写代码
特在此设置一个阐述思路的地方
既可以在这里进行正确解法的推理
也可以在赛后分享给别人,方便别人理解
//====================My_Solution====================*/
void solve () {
}
int32_t main () {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
Codeforces Round 1034 (Div. 3) F. Minimize Fixed Points
/*====================My_Solution====================//
题目要求构造一个长度为n的排列p,要求对于所有i ∈ [1, n]
最小化gcd(p[i], i) == 1的数量
为数不多的单切一遍过的题目,大致思路如下:
为了让gcd(p[i], i) != 1,那么最方便的方法是分个类
把2的倍数放在一起,交替着填充,3的倍数、5的倍数同理
如果遇到了4这样的合数,就把他归到2的倍数里
也就是说,我们只筛选质数的倍数,如果n的范围内没有该质数的倍数
那就认命吧,只能把这个质数放到这里了
而依据以上思路,我们发现,线性筛可以很完美的实现这个操作
筛选完素数以后,我们在素数数组中遍历他们的倍数
虽然是双层循环,但总共遍历的数字为n个,因此复杂度还是O(n)
开一个二维数组记录倍数,比如2的倍数就存放2 4 6 8 10这些
然后我们去“错位”填充,最终排列p中,2放4,4放6这样子
最后的2的倍数放上2,就可以了,代码如下
//====================My_Solution====================*/
std::vector<int> prim(N);
int now;
bool vis[N];
void init () {
for (int i = 2; i <= N; i++) {
if (!vis[i]) {
prim[++now] = i;
vis[i] = 1;
}
for (int j = 1; j <= now && prim[j] * i <= N; j++) {
vis[prim[j] * i] = 1;
if (i % prim[j] == 0) break;
}
}
}
void solve () {
int n;
std::cin >> n;
std::vector<int> st[n + 1];
std::vector<int> Vis(n + 1, 0);
std::vector<int> ans(n + 1);
int id = 1;
while (prim[id] < n) id++;
id--;
for (int i = id; i >= 1; i--) {
for (int j = 2; prim[i] * j <= n; j++) {
if (Vis[prim[i] * j]) continue;
st[prim[i]].push_back(prim[i] * j);
Vis[prim[i] * j] = 1;
}
}
for (int i = 1; i <= n; i++) {
int len = st[i].size();
if (len) {
ans[i] = st[i][0];
for (int j = 0; j < len - 1; j++) {
ans[st[i][j]] = st[i][j + 1];
}
ans[st[i][len - 1]] = i;
}
}
for (int i = 1; i <= n; i++) {
if (!ans[i]) {
ans[i] = i;
}
std::cout << ans[i] << " \n"[i == n];
}
}
Codeforces Round 1029 (Div. 3) E. Lost Soul
/*====================My_Solution====================//
题目给定两个长度相等的序列a和b,可以选择以下操作
1. 让a[i] = b[i + 1]
2. 让b[i] = a[i + 1]
除此之外你还有最多一次的机会,去删除任意一个索引x,即删除a[x] b[x]
你的目的是通过无限次的操作,让a[i] == b[i]的数量最大化
我们可以发现,如果序列中有a[i] == b[i],那么他们左边的全部都可以转化
我们还可以发现,如果序列中有a[i] == a[j]并且i和j中间相隔的数字为0或偶数,那么i左边也都可以转化
但是,我们是可以删除一列的,也就是说,只要存在a[i] == a[j],那么左边的就可以随便转化!
我们只需要判断是否存在a[i] == a[j]或者b[i] == b[j]就可以了
开一个seen数组记录当前数字是否出现过即可实现。代码如下
//====================My_Solution====================*/
void solve () {
int n;
std::cin >> n;
std::vector<int> a(n + 1), b(n + 1);
std::vector<int> seen(n + 1, 0);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
for (int i = 1; i <= n; i++) {
std::cin >> b[i];
}
if (a[n] == b[n]) {
std::cout << n << '\n';
return;
}
int ans = 0;
for (int i = n - 1; i >= 1; i--) {
if (a[i] == b[i] || a[i] == a[i + 1] || b[i] == b[i + 1] || seen[a[i]] || seen[b[i]]) {
ans = i;
break;
}
seen[a[i + 1]] = seen[b[i + 1]] = 1;
}
std::cout << ans << '\n';
}
Codeforces Round 1031 (Div. 2) C. Smilo and Minecraft
/*====================My_Solution====================//
给定一个矩阵,其中有石头和矿物,现在告诉你TNT的爆炸半径
在爆炸边缘的矿物可以收集,你可以无限爆破,请计算出最多能收集的矿物数量
也是一道思维题,乍一看不好计算,但我们可以发现
除了第一次爆炸会不可避免地造成损失,剩下的矿物均可以精准爆破!
所以我们的目的就是算出第一次爆炸的最小损失,然后用总矿物数减去他就可以了
我们可以用二维前缀和的方式快速计算每次爆炸的损失
然后遍历一遍矩阵,找到最小损失就可以了,代码如下
//====================My_Solution====================*/
void solve () {
int n, m, k;
std::cin >> n >> m >> k;
std::vector map(n + 1, std::vector<char>(m + 1));
std::vector sum(n + 1, std::vector<int>(m + 1));
int all = 0, lose = inf;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
std::cin >> map[i][j];
if (map[i][j] == 'g') all++;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + (map[i][j] == 'g');
}
}
auto ask = [&] (int x, int y) {
int x1, y1, x2, y2;
x1 = std::max(0, x - k);
y1 = std::max(0, y - k);
x2 = std::min(n, x + k - 1);
y2 = std::min(m, y + k - 1);
return sum[x2][y2] - sum[x1][y2] - sum[x2][y1] + sum[x1][y1];
};
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (map[i][j] == '.') {
lose = std::min(ask(i, j), lose);
}
}
}
std::cout << all - lose << '\n';
}
Codeforces Round 1026 (Div. 2) D. Fewer Batteries
/*====================My_Solution====================//
给定一个图,每个点都可以补充0~b[i]的电池,每个边如果要通过,对当前电池数有要求
要求找出可到达终点的最少电池数量,否则就输出-1
看似像是最短路,但其实这道题是一个二分答案
看到了“最少”,那么其实大概率就是二分最优解的思路
用二分的方式枚举答案,然后在check函数中去检测这个答案行不行
检测的方式是图上dp,f[i]表示到达点i能拿到的最大电池数量
如果f[n]最后是-1,没被更新,就意味着这个答案不行
//====================My_Solution====================*/
typedef struct node {
int dot, bat;
} node;
void solve () {
int n, m;
std::cin >> n >> m;
std::vector<int> b(n + 1);
std::vector<node> graph[n + 1];
std::vector<int> ind(n + 1, 0);
for (int i = 1; i <= n; i++) {
std::cin >> b[i];
}
for (int i = 1; i <= m; i++) {
int u, v, w;
std::cin >> u >> v >> w;
graph[u].push_back({v, w});
ind[v]++;
}
auto check = [&] (int mid) {
std::vector<int> f(n + 1, -1);
f[1] = std::min(mid, b[1]);
for (int now = 1; now <= n; now++) {
for (auto [next, bat] : graph[now]) {
if (f[now] >= bat) {
f[next] = std::max(f[next], std::min(mid, f[now] + b[next]));
}
}
}
return f[n] != -1 ? 1 : 0;
};
int l = 0, r = 1e9 + 10, ans = -1;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
std::cout << ans << '\n';
}
Educational Codeforces Round 178 (Rated for Div. 2) E. Unpleasant Strings
/*====================My_Solution====================//
给定一个母字符串,然后给出一些子字符串
计算这些子字符串需要添加最少几个字符,让其不再是母字符串的子序列(子序列即可不连续)
一道神奇的dp,暂未掌握
//====================My_Solution====================*/
void solve () {
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
std::vector next(n + 2, std::vector<int>(k, n));
std::vector<int> f(n + 1, 0);
for (int i = n - 1; i >= 0; i--) {
next[i] = next[i + 1];
int max = *max_element(next[i].begin(), next[i].end());
f[i] = f[max] + 1;
next[i][s[i] - 'a'] = i;
}
int q;
std::cin >> q;
while (q--) {
std::string t;
std::cin >> t;
int id = -1;
for (int i = 0; i < t.size(); i++) {
id = next[id + 1][t[i] - 'a'];
}
std::cout << f[id] << '\n';
}
}
Codeforces Round 1019 (Div. 2) C. Median Splits
/*====================My_Solution====================//
给定一个数组,判断能否划分为三个连续子数组
使得三个子数组的中位数的中位数是否<= k
这道题的思路挺有意思
先遍历原数组,如果a[i] <= k,那么就改为1,否则变成-1
这样的话,我们可以通过区间和的方式判断,当前区间中位数和k的关系
接下来分三种情况:
1. 从前往后遍历,如果前缀和>= 0,break,假设此时遍历到i
然后从后往前遍历,如果后缀和>= 0,break,假设此时遍历到j
以上两条同时满足且i < j,那么输出YES并return
2. 从前往后,然后再从前往后……
3. 从后往前,然后再从后往前……
如果以上三种情况任意一种满足,就是YES;如果均不满足,输出NO
//====================My_Solution====================*/
void solve () {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
int x;
std::cin >> x;
a[i] = x <= k ? 1 : -1;
}
// Condition 1
int sum1 = 0, sum2 = 0;
int flag1 = 0, flag2 = 0;
for (int i = 1; i <= n; i++) {
sum1 += a[i];
if (sum1 >= 0) {
flag1 = i;
break;
}
}
for (int i = n; i >= 1; i--) {
sum2 += a[i];
if (sum2 >= 0) {
flag2 = i;
break;
}
}
if (flag1 && flag2 && flag1 < flag2) {
std::cout << "YES\n";
return;
}
// Condition 2
sum1 = 0;
for (int i = 1; i <= n; i++) {
sum1 += a[i];
if (sum1 >= 0) {
sum2 = 0;
for (int j = i + 1; j <= n; j++) {
sum2 += a[j];
if (sum2 >= 0 && i < j && j < n) {
std::cout << "YES\n";
return;
}
}
}
}
// Condition 3
sum1 = 0;
for (int i = n; i >= 1; i--) {
sum1 += a[i];
if (sum1 >= 0) {
sum2 = 0;
for (int j = i - 1; j >= 1; j--) {
sum2 += a[j];
if (sum2 >= 0 && i > j && j > 1) {
std::cout << "YES\n";
return;
}
}
}
}
std::cout << "NO\n";
}
Neowise Labs Contest 1 (Codeforces Round 1018, Div. 1 + Div. 2) C. Wonderful City
/*====================My_Solution====================//
给定一个n * n的矩阵,现在有以下两种操作
1. 将第i行所有值+1,代价为a[i]
2. 将第j列所有值+1,代价为b[j]
要求更改后的矩阵,任意位置的数字都不能与其上下左右相邻的数字相同
考虑dp,这道题的核心就是:行列操作互不影响
1. f[i][0] = std::min(f[i - 1][0], f[i - 1][1])
2. f[i][1] = std::min(f[i - 1][0] + a[i], f[i - 1][1] + a[i])
这道题所具有的特殊情况为:
1. 如果该行比上一行多1,那么上一行不能动
2. 如果该行比上一行少1,那么该行不能动
//====================My_Solution====================*/
void solve () {
int n;
std::cin >> n;
std::vector h(n + 1, std::vector<ll>(n + 1));
std::vector<ll> a(n + 1), b(n + 1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
std::cin >> h[i][j];
}
}
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
for (int i = 1; i <= n; i++) {
std::cin >> b[i];
}
std::vector f1(n + 1, std::vector<ll>(2, INF));
std::vector f2(n + 1, std::vector<ll>(2, INF));
f1[1][0] = 0, f1[1][1] = a[1];
f2[1][0] = 0, f2[1][1] = b[1];
ll ans = 0;
// row
for (int i = 2; i <= n; i++) {
int x = 1, y = 1, z = 1;
for (int j = 1; j <= n; j++) {
ll d = h[i][j] - h[i - 1][j];
if (d == 1) x = 0;
if (d == 0) y = 0;
if (d == -1) z = 0;
}
// 当不存在“第i-1行比第i行少1”的情况时
if (x) f1[i][0] = std::min(f1[i][0], f1[i - 1][1]);
// 当不存在“第i-1行和第i行相同”的情况时
if (y) f1[i][0] = std::min(f1[i][0], f1[i - 1][0]);
if (y) f1[i][1] = std::min(f1[i][1], f1[i - 1][1] + a[i]);
// 当不存在“第i-1行比第i行多1”的情况时
if (z) f1[i][1] = std::min(f1[i][1], f1[i - 1][0] + a[i]);
}
ans += std::min(f1[n][1], f1[n][0]);
// col
for (int j = 2; j <= n; j++) {
int x = 1, y = 1, z = 1;
for (int i = 1; i <= n; i++) {
ll d = h[i][j] - h[i][j - 1];
if (d == 1) x = 0;
if (d == 0) y = 0;
if (d == -1) z = 0;
}
if (x) f2[j][0] = std::min(f2[j][0], f2[j - 1][1]);
if (y) f2[j][0] = std::min(f2[j][0], f2[j - 1][0]);
if (y) f2[j][1] = std::min(f2[j][1], f2[j - 1][1] + b[j]);
if (z) f2[j][1] = std::min(f2[j][1], f2[j - 1][0] + b[j]);
}
ans += std::min(f2[n][0], f2[n][1]);
if (ans >= INF) std::cout << -1 << '\n';
else std::cout << ans << '\n';
}
Codeforces Round 1017 (Div. 4) G. Chimpanzini Bananini
/*====================My_Solution====================//
给定一个序列,在每次操作后输出他的加权和
略,模拟就行了
//====================My_Solution====================*/
void solve () {
int q;
std::cin >> q;
i64 sum = 0, len = 0; // with no weight
i64 weigh_sum = 0, re_weigh_sum = 0;
std::deque<i64> pos, neg;
while (q--) {
int op;
std::cin >> op;
if (op == 1) {
weigh_sum = weigh_sum + sum - len * pos.back();
re_weigh_sum = re_weigh_sum - sum + len * neg.front();
pos.push_front(pos.back());
neg.push_back(neg.front());
pos.pop_back(), neg.pop_front();
}
else if (op == 2) {
std::swap(weigh_sum, re_weigh_sum);
std::swap(pos, neg);
}
else {
i64 k;
std::cin >> k;
len++;
weigh_sum += k * len;
re_weigh_sum = re_weigh_sum + sum + k;
sum += k;
pos.push_back(k);
neg.push_front(k);
}
std::cout << weigh_sum << '\n';
}
}
Codeforces Round 1017 (Div. 4) F. Trulimero Trulicina
/*====================My_Solution====================//
神秘构造,暂未掌握
//====================My_Solution====================*/
void solve () {
int n, m, k;
std::cin >> n >> m >> k;
if (m % k == 0) {
for (int i = 1; i <= n; i++) {
if (i % 2 == 1) {
int cnt = 1;
for (int j = 1; j <= m; j++) {
std::cout << cnt << ' ';
cnt++;
if (cnt == k + 1) cnt = 1;
}
std::cout << '\n';
}
else {
int cnt = 2;
for (int j = 1; j <= m; j++) {
std::cout << cnt << ' ';
cnt++;
if (cnt == k + 1) cnt = 1;
}
std::cout << '\n';
}
}
}
else {
int cnt = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
std::cout << cnt << ' ';
cnt++;
if (cnt == k + 1) cnt = 1;
}
std::cout << '\n';
}
}
}
Codeforces Round 1012 (Div. 2) C. Dining Hall
/*====================My_Solution====================//
一个“食堂就坐”问题,大意为,现在有n个人要进入食堂就坐
每个人为属性0或者1,属性0的比较社恐,会找到距离最近的空桌子就坐
属性为1的比较外向,会找到距离最近的空座位就坐
要求按顺序输出每位客人的位置
我们可以模拟一遍这个过程,先将所有可能的位置加到一个结构体数组seat里
并且我们可以将桌子和座位的坐标联系起来,我们定义桌子的坐标为[x, y]
然后令i = 3 * x + 1, j = 3 * y + 1
那么座位的坐标就是[i, j]、[i, j + 1]、[i + 1, j]、[i + 1, j + 1]
我们也可以用座位横纵坐标各 / 3得到桌子坐标
然后我们可以发现,到达座位的步数也是有规律的
左下角为 i + j - 1 步
左上角为 i + j 步
右下角为 i + j 步
右上角为 i + j + 3 步
我们通过枚举桌子,把这些座位和对应的步数都push进seat数组中
最后按照步数排个序,开一个桌子的标记数组和座位的标记数组
然后枚举位置即可
//====================My_Solution====================*/
typedef struct node {
int x, y, num;
} node;
std::vector<node> seat;
void init () {
for (int x = 0; x <= 320; x++) {
for (int y = 0; y <= 320; y++) {
int i = 3 * x + 1;
int j = 3 * y + 1;
int num = i + j - 1;
seat.push_back({ i, j, num });
seat.push_back({ i + 1, j, num + 1 });
seat.push_back({ i, j + 1, num + 1 });
seat.push_back({ i + 1, j + 1, num + 4 });
}
}
std::sort(seat.begin(), seat.end(), [&] (node a, node b) {
if (a.num != b.num) return a.num < b.num;
else {
if (a.x != b.x) return a.x < b.x;
else return a.y < b.y;
}
});
}
void solve () {
int n;
std::cin >> n;
int m = sqrtl(2 * n) + 3;
std::vector<int> a(n + 1);
std::vector vis_seat(m * 3, std::vector<int>(m * 3, 0));
std::vector vis_table(m, std::vector<int>(m, 0));
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
int p1 = 0, p2 = 0;
for (int i = 1; i <= n; i++) {
int X, Y;
if (a[i] == 1) {
while (vis_seat[seat[p1].x][seat[p1].y]) p1++;
X = seat[p1].x, Y = seat[p1].y;
}
else {
while (vis_table[seat[p2].x / 3][seat[p2].y / 3]) p2++;
X = seat[p2].x, Y = seat[p2].y;
}
vis_seat[X][Y] = 1;
vis_table[X / 3][Y / 3] = 1;
std::cout << X << ' ' << Y << '\n';
}
}
Codeforces Round 1012 (Div. 1) A. Simple Permutation
/*====================My_Solution====================//
神秘构造题,暂未掌握
//====================My_Solution====================*/
inline bool prime (int x) {
if (x == 1) return 0;
if (x == 2) return 1;
for (int i = 2; i <= sqrtl(x); i++) {
if (x % i == 0) {
return 0;
}
}
return 1;
}
void solve () {
int n;
std::cin >> n;
int len = n / 3 - 1;
int tmp = (len - 1) / 2;
int p = -1;
for (int i = 1; i <= n; i++) {
if (i - tmp >= 1 && prime(i)) {
p = i;
break;
}
}
std::vector<int> ans;
std::vector<int> vis(n + 1, 0);
ans.push_back(p);
vis[p] = 1;
for (int i = 1; p + i <= n && p - i > 0; i++) {
ans.push_back(p - i);
ans.push_back(p + i);
vis[p - i] = 1;
vis[p + i] = 1;
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
ans.push_back(i);
}
}
for (auto it : ans) {
std::cout << it << ' ';
}
std::cout << '\n';
}
Codeforces Round 1011 (Div. 2) C. Serval and The Formula
/*====================My_Solution====================//
给定x和y,要求找到一个k
使得(x + k) + (y + k) == (x + k) ^ (y + k)
对于 a + b == a ^ b
说明 对于a和b上任何一个二进制位,都没有同时为1的情况
也就是说 a & b == 0
因为2的幂的二进制表示总是1000000000这样的
并且2的幂,与任何小于它的正整数都没有公共位
所以我们的目的是,让x或者y,其中一个+k之后变成2的幂次
//====================My_Solution====================*/
void solve () {
i64 x, y;
std::cin >> x >> y;
if (x == y) {
std::cout << "-1\n";
return;
}
std::cout << (1LL << 48) - std::max(x, y) << '\n';
}
Teza Round 1 (Codeforces Round 1015, Div. 1 + Div. 2) D. Arcology On Permafrost
/*====================My_Solution====================//
猜想:
数组的MEX和m有关系
6th样例因为要删掉6个子数组,所以最大值不会很高
7th样例虽然删的很少,但最高也只能到5
因此可以看出,越想向上走,每一步的“花费”增加,并且增加很多
1. 假设删1个的数组
那么我们需要将n平分为2个重复数组
这样无论删那边儿,都有一边儿保持完整的递增,MEX不受影响
2. 假设删2个的数组
那么我们需要将n平分为3个重复数组
这样无论删那边儿,都有一边儿保持完整的递增,MEX不受影响
因此,我们认为,每个数应出现m + 1次
根据6th和7th样例,可以证明上述结论是正确的
目前看来,应该从上往下来,先保证最大的数字有m + 1个
或许子数组长度k,决定了每个相同的数应该相隔至少多远?
标答:
首先,f(a)最大为n - m * k,这是最理想状态
其次,为了保证“不断层”,f(a)应当为n / (m + 1),也就是每个数字必须至少出现m + 1次
因此,f(a)最大值 = std::min(n - m * k, n / (m + 1))
分为两种情况:
1. n - m * k < n / (m + 1) ==> n < (m + 1) * k ==> n - m * k < k
也就是a的最终长度是小于k的,此时只能令ans[i] = i % k
2. n - m * k >= n / (m + 1) ==> n >= (m + 1) * k ==> n / (m + 1) >= k
因此每个相同数字都能至少有k的距离,所以直接依次从0输出到n / (m + 1)即可
//====================My_Solution====================*/
void solve () {
int n, m, k;
std::cin >> n >> m >> k;
if (n < (m + 1) * k) {
for (int i = 0; i < n; i++) {
std::cout << i % k << " \n"[i == n - 1];
}
}
else {
for (int i = 0; i < n; i++) {
std::cout << i % (n / (m + 1)) << " \n"[i == n - 1];
}
}
/* 更简单的写法 */
// for (int i = 0; i < n; ++i) {
// std::cout << i % (n < (m + 1) * k ? k : n / (m + 1)) << " \n"[i == n - 1];
// }
}
Codeforces Round 1010 (Div. 2, Unrated) B. Floor or Ceil
/*====================My_Solution====================//
执行n + m次操作,其中n次是 / 2下取整,m次是 / 2上取整
当前数如果 % 2 == 0,那么下取整
如果 % 2 != 0,那么上取整
理论上,这样算出来的是最大的
首先,如果x < (1 << (n + m)),那么直接输出1即可,但好像也不一定
所以先全上取整,再全下取整是最大吗?
把数字x转化为二进制,假设x = 101010011101010
1. x = x >> 1
2. x = (x + 1) >> 1
正确的思路是,先n后m,或者先m后n,算出来的直接就是答案
//====================My_Solution====================*/
void solve () {
int x, n, m;
std::cin >> x >> n >> m;
auto dn = [&] (i64 base, i64 time) {
while (time--) {
if (!base) return base;
base = base >> 1;
}
return base;
};
auto up = [&] (i64 base, i64 time) {
while (time--) {
if (base <= 1) return base;
base = base + 1 >> 1;
}
return base;
};
int ans1 = up(dn(x, n), m);
int ans2 = dn(up(x, m), n);
std::cout << ans2 << ' ' << ans1 << '\n';
}
Educational Codeforces Round 175 (Rated for Div. 2) D. Tree Jumps
/*====================My_Solution====================//
单切的题,当时尝试了很多次,一步步修正,一点点优化
最后看到Accepted的那一刻真的很激动,或许这就是进步吧
在移动过程中,从u移动到v,需满足不相邻且dv = du + 1
那么我们可以用bfs去预处理每一层的结点数,然后到时候依次相乘?
虽然感觉有点像树上dp,但还是先用bfs试一下
dp数组在计算时超时了,显然是因为我们遍历了上一层,不够高效
这时我们可以用前缀和优化
但是,计算区间和也是需要知道dp数组才可以,只能通过dp数组递推
我们可以在dp数组计算完当前层之后再计算sum数组
这样下一层计算dp时可以直接调用上一层的sum数组
这样每个点只会被访问两边,总复杂度O(2 * n)
//====================My_Solution====================*/
void solve () {
int n;
std::cin >> n;
std::vector<i64> fa(n + 10), g[n + 10], son[n + 10];
std::vector<i64> dp(n + 10), sum(n + 10);
sum[1] = 1;
for (int i = 2; i <= n; i++) {
std::cin >> fa[i];
g[fa[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (fa[i] == 1 || i == 1) {
dp[i] = 1;
}
}
std::queue<pii> q;
q.push({1, 1});
int last_step = 0;
while (q.size()) {
auto [now, step] = q.front();
q.pop();
son[step].push_back(now);
if (step > last_step) {
for (auto last_dot : son[last_step]) {
sum[last_step] = sum[last_step] + dp[last_dot]; // 这里不能对mod取模!!
}
last_step = step;
}
if (step > 2) {
dp[now] = (dp[now] + sum[step - 1] - dp[fa[now]]) % mod;
}
for (auto next : g[now]) {
q.push({next, step + 1});
}
}
i64 ans = 0;
for (int i = 1; i <= n; i++) {
ans = (ans + dp[i]) % mod;
}
std::cout << ans << '\n';
}
Codeforces Round 1003 (Div. 4) E. Skibidus and Rizz
/*====================My_Solution====================//
要求用n个0和m个1构造一个长度为n + m的二进制字符串
要求该字符串的所有连续子串的最大平衡值为k
平衡值为该子串中0的个数和1的个数的差值(绝对值)
首先我们可以知道,如果k == std::max(n, m),则一定可以满足
如果k > std::max(n, m),则一定不可以;那k < std::max(n, m)呢?
此时我们需要将01隔开,以限制平衡值的最大值
为了凑出平衡值k,我们需要取n和m中的较大者做上限
假设n和m中,m大,那么我们需要取m个1和m - k个0
将这取出来的0和1交叉分布,作为平衡值最大的子串
那剩下的n - m + k个字符呢?左右两端各一半?
//====================My_Solution====================*/
void solve () {
int n, m, k;
std::cin >> n >> m >> k;
if (k > std::max(n, m)) {
std::cout << "-1\n";
return;
}
if (k == std::max(n, m)) {
for (int i = 1; i <= n + m; i++) {
std::cout << (i <= n ? '0' : '1');
}
std::cout << '\n';
return;
}
std::deque<char> ans;
if (n > m) {
if (m < n - k) {
std::cout << "-1\n";
return;
}
// 在n个0中插入n - k个1
int cnt0 = n;
int cnt1 = n - k;
while (ans.size() < n + n - k) {
if (cnt0) {
ans.push_back('0');
cnt0--;
}
if (cnt1) {
ans.push_back('1');
cnt1--;
}
}
cnt1 = m - n + k;
while (cnt1) {
if (cnt1) {
ans.push_back('1');
cnt1--;
}
if (cnt1) {
ans.push_front('1');
cnt1--;
}
}
}
else {
if (n < m - k) {
std::cout << "-1\n";
return;
}
// 在m个1中插入m - k个0
int cnt1 = m;
int cnt0 = m - k;
while (ans.size() < m + m - k) {
if (cnt1) {
ans.push_back('1');
cnt1--;
}
if (cnt0) {
ans.push_back('0');
cnt0--;
}
}
cnt0 = n - m + k;
while (cnt0) {
if (cnt0) {
ans.push_back('0');
cnt0--;
}
if (cnt0) {
ans.push_front('0');
cnt0--;
}
}
}
for (auto it : ans) {
std::cout << it;
}
std::cout << '\n';
}
IAEPC Preliminary Contest (Codeforces Round 999, Div. 1 + Div. 2) D. Kevin and Numbers
/*====================My_Solution====================//
给定长度为n的序列a和长度为m的序列b,能否通过操作将a变成b
操作是:选择a中差的绝对值<= 1的两个数字,将这俩数字删除,再加入a + b
我们可以反着来,将b中的数字进行拆解
用两个优先队列进行存储,如果b首 < a首,则一定不可以
如果等于,a首b首都删除,否则将b首拆成两个一半
一直模拟下去,最后如果ab均为空,就可以
//====================My_Solution====================*/
void solve () {
i64 n, m;
std::cin >> n >> m;
std::priority_queue<i64> a, b;
for (int i = 1; i <= n; i++) {
i64 x;
std::cin >> x;
a.push(x);
}
for (int i = 1; i <= m; i++) {
i64 x;
std::cin >> x;
b.push(x);
}
while (!a.empty()) {
if (b.empty() || b.size() > a.size()) {
std::cout << "No\n";
return;
}
i64 x = b.top();
b.pop();
if (x < a.top()) {
std::cout << "No\n";
return;
}
else if (x == a.top()) {
a.pop();
}
else {
b.push(x >> 1);
b.push(x + 1 >> 1);
}
}
if (!b.empty()) {
std::cout << "No\n";
return;
}
std::cout << "Yes\n";
}