64道CodeForces 1600 - 1700的题目

插个眼,等刷够64道题以后,看看是个什么水平
(做题进度:40/64,复盘进度:17/64)


目前从中学到的思想 / 方法 / 技巧有:

  1. 1600无难题,只要想学,就一定能学会
  2. 先把思路想明白了再去写代码!无论花多长时间去推思路都无所谓,只要想明白了,写代码就是分分钟的事情,贸然下手只能喜提WA2
    补:推完了再写!不要感觉推的差不多了就开始写,要完全推出来再去写
  3. 贪心的思路想不通一定要试试dp,不要觉得dp是什么很高深的东西,dp更多的是一种思路、思维、思想,很多时候用dp思想考虑问题反而会变简单
    补:dp的思路想不通也要试试贪心!不要盲目dp,并非所有问题都是dp都会更好分析,如果某个问题和贡献有关,那么很可能是贪心的思路
  4. 根据时间复杂度分析问题,O(n) O(nlogn) O(n^2),根据复杂度的需求去猜测和设计解法
  5. 在面对很多“求最小”的问题,不只是贪心,也有可能是二分枚举最优解(二分答案)
  6. 在面对一些区间问题的时候,可能要用到前缀和的思想,比如ans = cal(r) - cal(l - 1)这样
    或者是dp的思路,比如f[i]比f[i - 1]多出来[1, i][2, i] ... [i, i]共i个区间这样
  7. 容斥原理,详见https://www.cnblogs.com/WelCo/p/19003709
  8. 正确的解法一般不会有很多特判,如果写了一堆特判要看看是不是自己思路想错了

本人的板子
#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";
}







































posted @ 2025-07-30 15:53  _彩云归  阅读(85)  评论(0)    收藏  举报