Loading

cf 刷题杂记(1)

Codeforces Round 969 (Div. 2)

之前某篇题解一语成谶掉分了,赶在正式开学前打回蓝名 QwQ

C. Dora and C++ (C)

卡了很久才想起来裴蜀定理,期间还写了个假做法,不好评价。

\(ax + by = k\times gcd(a, b)\) 有解,可以将序列中任意两个数的差减小至 \(gcd(a, b)\) 以下。从相对性角度考虑,令 \(c_i\mod gcd(a, b)\) 后重新排序,则 \(ans = c_n' - c_1'\);再贪心地从首项开始加上 \(gcd\),有 \(ans = min(ans, c_i' + gcd - c_{i + 1})\).

D. Iris and Game on the Tree (D)

妙妙博弈题,写的比C题还快一点,果然我更擅长猜结论,基础还是太不扎实了

观察发现规律:任意字符串的权重只与其首尾字符有关,首尾字符相同则权重为 \(0\),反之为 \(1\);因此整棵树的权重只与根节点和叶子节点有关。当根节点值确定时,最优操作显然容易判断,而根节点不确定时,设叶子节点中已经确定的值数量为 \(cnt_0,cnt_1\),当二者不相等时,先手先行确定根节点更加有利;而 \(cnt_0 = cnt_1\) 时,若叶子节点剩余问号数量为奇数,确定根节点会导致步数落后,因此双方都会尽可能地先填中间节点的值、将确定根节点的步骤留给对方,考虑中间结点数量的奇偶性即可。

E. Iris and the Tree (E)

题目要求每次操作后都输出答案,此时动态维护很可能比直接求解更优。树上节点按照dfs顺序分布,每条边一定会被下方节点与某个向上回溯的叶子节点经过、被计算两次。数组 \(c_1,c_2\) 记录经过每条边的两个节点编号,\(len\) 记录每个节点经过的边数,对于所有确定的边,其总贡献为已知边权和 \(sum\) 的两倍;设此时仍然无法确定路径的节点有 \(cur\) 个,应将所有剩余边权算入这 \(cur\) 条路径中,即 \(cur \times (w - sum)\).

    dep[1] = 1;
    len[1] = c1[1] = 0;
    for(int i = 2; i <= n; i++) {
        scanf("%d", &f[i]);
        dep[i] = dep[f[i]] + 1;
        len[i] = c1[i] = 0;
    }
    for(int i = 1; i <= n; i++) {
        int x = i, y = i + 1;
        if(y > n) y = 1;
        while(x != y) {
            if(dep[x] < dep[y]) swap(x, y);
            if(c1[x]) c2[x] = i;
            else c1[x] = i;
            x = f[x], len[i]++;
        }
    }
    ll sum = 0, cur = n;
    for(int i = 1; i < n; i++) {
        int x;
        ll y;
        scanf("%d%lld", &x, &y);
        sum += y;
        len[c1[x]]--, len[c2[x]]--; // len = 0 即该节点路径全部确定
        if(len[c1[x]] == 0) cur--;
        if(len[c2[x]] == 0) cur--;
        printf("%lld ", sum * 2 + cur * (w - sum));
    }

F. Eri and Expanded Sets (F)

考虑最终集合 \(a\) 中的元素,对顺序排列形成的 \(a\) 数组取差分 \(d\),由奇偶规则得 \(d_i\) 为奇数;对于相邻的 \(d_i\)\(d_{i + 1}\),若 \(d_i\neq d_{i + 1}\),可以产生新的元素 \(a_i' = (a_i + a_{i + 2}) / 2\),且 \(a_i' \neq a_{i + 1}\),因此 \(d_i = d_{i + 1}\) 一定成立,即最终 \(a\) 为等差数列。而 \(a_i' = (a_i + a_{i + 2}) / 2\) 时,新产生 \(d_i' = |d_i - d_{i + 1}|\),由裴蜀定理可得,等差数列的公差为原数组差分后、各元素gcd的最大奇数因子。因此对于一个合法的子序列,其差分数组 \(gcd \{d_i\} = 2^x\),且 \(l\)\(r\) 的范围扩大时,合法子序列的gcd只减不增、不可能变成非法,可以用st表维护区间gcd求解。

int a[N], eq[N];
int st[N][20];
int query(int l, int r) {
    int k = __lg(r - l + 1);
    // 希望我不要再写挂st表(闭目)(祈祷)
    return __gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
void solve() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    if(n == 1) {
        printf("1\n");
        return;
    }
    n--; // 方便考虑差分数组
    for(int i = 1; i <= n; i++) {
        st[i][0] = abs(a[i + 1] - a[i]);
    }
    for(int k = 1; k <= __lg(n); k++) {
        for(int i = 1; i + (1 << k) - 1 <= n; i++) {
            st[i][k] = __gcd(st[i][k - 1], st[i + (1 << k - 1)][k - 1]);
        }
    }
    eq[n + 1] = n + 1;
    for(int i = n; i; i--) {
        if(a[i] == a[i + 1]) eq[i] = eq[i + 1];
        else eq[i] = i;
    }
    int l = 1, r = 1;
    ll ans = 1; // 最后一项
    for(int i = 1; i <= n; i++) {
        l = max(l, eq[i]);
        r = max(r, l);
        // 二进制中只有一个1,即为2的x次幂
        while(r <= n && __builtin_popcount(query(l, r)) > 1) r++;
        // i~r 至 i~n 满足,i~i 至 i~eq[i] 满足
        ans += n - r + 1 + eq[i] - i + 1;
    }
    printf("%lld\n", ans);
}

Educational Codeforces Round 169 (Rated for Div. 2)

E. Not a Nim Problem (E)

sg函数打表题,将每堆石子视为一个单独的游戏,计算其sg函数,发现其分布具有规律性。具体而言,所有偶数的sg函数为0,奇数的sg函数与其最小质因数相等;除2以外,第 \(i\) 个质数的sg函数等于 \(i\);特别的,1的sg函数为1. 可在线性筛素数的同时维护sg函数值。对于整个序列,其sg函数为每堆石子sg函数的异或和。(这个规律应该也能证,但证明好麻烦不写了

Codeforces Round 965 (Div. 2)

C. Perform Operations to Maximize Score (C)

又是中位数的题,非常好没写出来,不知道在干什么。为了使最终权值最大,将 \(k\) 全部用于改变最大值或改变中位数是最优的,若同时改变,则权值变化量不会额外增大。改变最大值的操作较为简单,对于改变中位数的操作,可以二分答案求出最大可能的中位数。

“对于一个序列直接求中位数需要排序,不便于寻找最优解,而对于一个确定的数,有相对容易的方法验证其是否为中位数。考虑二分答案。”(自己写的东西都不记得,不好评价)

D. Determine Winning Islands in Race (D)

代码不难写,但思维很妙。由于Bessie每次只能由岛屿 \(i\) 到达岛屿 \(i + 1\),且Bessie先行动,Elsie为了获胜,必须提前到达Bessie前方的岛屿。预处理出Elsie到达每个岛的最短时间 \(d_i\),Bessie获胜时应对所有 \(i > s\) 满足 \(d_i \geq i - s\),即 \(s \geq max(i - d_i)\). 而在 \(d_i\) 时间内,一部分岛屿会被Bessie破坏、Elsie无法通过,因此从 \(n - 1\)\(1\) 递推计算,multiset维护 \(i - d_i\) 的最大值,递推过程中从集合中动态删除Bessie历经岛屿所影响的 \(i - d_i\).

    while(m--) {
        int x, y;
        scanf("%d%d", &x, &y);
        v[x].push_back(y);
        r[y].push_back(x); // 同时保存反边,便于倒序遍历时操作
    }
    bfs(); // 边权为1的最短路
    multiset <int> s;
    for(int i = n; i; i--) {
        for(auto t : r[i]) {
            int mx = i - d[t] - 1;
            s.insert(mx);
            e[t].push_back(mx); // 保存t节点会影响的(i - d[i])值
        }
        for(auto t : e[i]) {
            s.erase(s.find(t)); // 经过i时删除i影响的值
        }
        if(i < n) {
            int x = -1;
            if(s.size()) {
                auto it = s.end();
                it--;
                x = *it;
            }
            if(i >= x) ans[i] = 1;
            else ans[i] = 0;
        }
    }

Educational Codeforces Round 168 (Rated for Div. 2)

E. Level Up (E)

好一道抽象的题目)由于 \(k\) 增大时等级增长变慢,两者为线性关系,对于任意 \(i\) 都存在一个确定的值 \(k_i\)\(x\geq k_i\) 时,第 \(i\) 个怪物会与主角战斗,否则其将逃跑;二分答案预处理出 \(k_1\)\(k_n\),之后可 \(O(1)\) 查询。对所有 \(k\) 值建立线段树,下标 \(l\) 位置保存 \(k = l\) 时已经与主角发生过战斗的怪物数量,从 \(1\)\(n\) 递推计算,每次二分过程中单点查询 \(num_k\),满足 \(num_k / k + 1 \leq a[i]\)\(k\) 为合法值;求出 \(k_i\) 后,对于所有 \(k\geq k_i\),主角会与怪物战斗,区间修改 \(k_i\)\(n\) 即可。

现在我应该不会再打错线段树板子了,所以没放线段树代码

    b[1] = 1;
    modify(1, 1, n, 1, n, 1); // 初始化
    for(int i = 2; i <= n; i++) {
        int l = 1, r = n;
        while(l <= r) {
            int mid = (l + r) / 2;
            int x = query(1, 1, n, mid);
            if(x / mid + 1 <= a[i]) {
                b[i] = mid;
                r = mid - 1;
            } else l = mid + 1;
        }
        modify(1, 1, n, b[i], n, 1);
    }
posted @ 2024-09-03 19:53  Aderose_yr  阅读(17)  评论(0编辑  收藏  举报