Codeforces Round 1070 (Div. 2) A~D F

最近手感差的很,A能WA两发写20min,D调不出来,不过看别人的AC代码dp思路跟自己也不太一样...还是自己太菜了,加训div2了。

A. Operations with Inversions

Given an array a1,a2,...,ana_1, a_2, \ldots, a_na1,a2,...,an. In one operation, you can choose a pair of indices i,ji, ji,j such that 1≤i<j≤n1 \le i < j \le n1≤i<j≤n, ai>aja_i > a_jai>aj, and remove the element at index jjj from the array. After that, the size of the array will decrease by 111, and the relative order of the elements will not change.

Determine the maximum number of operations that can be performed on the array if they are applied optimally.

真是糖丸了,第一遍读错题了,后来发现思路错了,应该直接 O(n2)O(n^2)O(n2) 扫的。

cpp 复制代码
void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];

    int ans = 0;
    vector<int> st(n + 1);
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            if (a[i] > a[j]) {
                st[j] = 1;
            }
        }
    }

    cout << accumulate(range(st), 0LL) << endl;
}

B. Optimal Shifts

You are given a binary string s1s2...sns_1s_2 \ldots s_ns1s2...sn, containing at least one 1. You want to obtain a binary string of the same length, consisting only of 1s. To do this, you can perform the following operation any number of times:

Choose a number ddd (1≤d≤n1 \le d \le n1≤d≤n) and consider the string ttt as a cyclic right shift of the string sss by ddd, or, more formally, t=sn−d+1...sns1...sn−dt = s_{n - d + 1} \ldots s_{n}s_{1} \ldots s_{n - d}t=sn−d+1...sns1...sn−d. After that, for all jjj for which tj=1t_j = 1tj=1, perform sj:=1s_j := 1sj:=1. The described operation costs ddd coins, where ddd is the chosen shift amount.

Note that the positions jjj in the string sss, where initially sj=1s_j=1sj=1, remain equal to 111 even if tj=0t_j=0tj=0.

You need to determine the minimum number of coins that can be spent so that the string sss consists only of 1s after all operations.

看成一个环(首位相接),直接找相邻两个 1 中间有多少个 0;

也唐了,第一遍双指针没有更新 i 的位置,TLE了...

cpp 复制代码
void solve()
{
    int n;
    string s;
    cin >> n >> s;

    int i = 0;
    int cnt1 = 0, cnt2 = 0;
    while(s[i] == '0' && i < n) cnt1++, i++;

    i = n - 1;
    while(s[i] == '0' && i >= 0) cnt2++, i--;

    int ans = cnt1 + cnt2;
    for (int i = 0; i < n; i++) {
        if (s[i] == '0') {
            int j = i + 1;
            while(j < n && s[j] == '0') j++;
            j--;
            ans = max(ans, j - i + 1);
            i = j;
        }
    }

    cout << ans << endl;
}

C. Odd Process

You have nnn coins with denominations a1,a2,...,ana_1, a_2, \ldots, a_na1,a2,...,an and a natural number kkk. You also have a bag, which is initially empty, where you can place coins. You need to perform exactly kkk actions. In each action, you take one coin from those you have left and put it in your bag. After that, you can no longer take that coin.

At the same time, you have a cat that loves even numbers, so every time the sum of the denominations of the coins in your bag becomes even, your cat empties the bag, meaning it takes all the coins to a place known only to it, and the bag is empty again. Note that the bag is emptied every time the sum becomes even during the process of adding coins, not just at the very last moment.

Let your score be the sum of the denominations of the coins in the bag. Your task is to perform kkk actions such that your final score is maximized. Find the answer for all 1≤k≤n1 \le k \le n1≤k≤n.

简单来讲,得发现:

首先选一个奇数 ,接着一直选偶数,这样才能保证最后的结果最优。

但是如果偶数个数加上奇数的 1,也就是 num_even + 1 > k,则还需要一些别的操作来抵消多余的 k,可以发现需要的抵消奇数个数等于,(k - num_even - 1 + 1) / 2 * 2,然后就是再判断一堆边界问题,有点小麻烦。。

cpp 复制代码
void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    vector<vector<int>> vec(2, vector<int>(1, 0));
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        vec[a[i] & 1].push_back(a[i]);
    }
    auto pre = vec;

    sort(range_(vec[1])); sort(range_(vec[0]));
    int no = vec[1].size() - 1, ne = vec[0].size() - 1;
    for (int i = 1; i <= no; i++) {
        pre[1][i] = pre[1][i - 1] + vec[1][i];
    }
    for (int i = 1; i <= ne; i++) {
        pre[0][i] = pre[0][i - 1] + vec[0][i];
    }
    
    for (int k = 1; k <= n; k++) {
        int ans;
        if (ne + 1 < k) {
            int num = ((k - ne - 1 + 1) / 2) * 2;
            int k_ = k - num;  // even
            if (num == no) {
                ans = 0;
            } else {
                ans = vec[1].back();
                int i = max(0LL, k_ - 1);
                if (i > ne || k_ == 0) {
                    ans = (k & 1) ? vec[1].back() : 0;
                } else {
                    ans += pre[0][ne] - pre[0][ne - (i)];
                }
            }
        } else {
            if (no == 0) {
                ans = 0;
            } else {
                ans = vec[1].back();
                int i = k - 1;
                ans += pre[0][ne] - pre[0][ne - (i)];
            }
        }
        cout << ans << ' ';
    }
    cout << endl;
}

D. Fibonacci Paths

You are given a directed graph consisting of nnn vertices and mmm edges. Each vertex vvv corresponds to a positive number ava_vav. Count the number of distinct simple paths ∗^{\text{∗}}∗ consisting of at least two vertices, such that the sequence of numbers written at the vertices along the path forms a generalized Fibonacci sequence.

In this problem, we will consider that the sequence of numbers x0,x1,...,xkx_0, x_1, \ldots, x_kx0,x1,...,xk forms a generalized Fibonacci sequence if:

  • x0,x1x_0, x_1x0,x1 are arbitrary natural numbers.
  • xi=xi−2+xi−1x_i = x_{i - 2} + x_{i - 1}xi=xi−2+xi−1 for all 2≤i≤k2 \le i \le k2≤i≤k.

Note that a generalized Fibonacci sequence consists of at least two numbers.

Since the answer may be large, output it modulo 998 244 353998\,244\,353998244353.

∗^{\text{∗}}∗A simple path in a directed graph is a sequence of vertices v1,v2,...,vkv_1, v_2, \ldots, v_kv1,v2,...,vk such that each vertex in the graph appears in the path at most once and there is a directed edge from viv_ivi to vi+1v_{i+1}vi+1 for all i<ki < ki<k.

挺好的一道题目,可惜没写出来。

对于图上的DP问题我们往往不知道怎么下手,说白了就是不知道怎么设置初始状态,因为图上往往有环

对于这道题我们其实可以看成一种类似拓扑的结构:

因为斐波那契一定是递增的,所以我们可以从最小的一批元素下手,再从它们进行扩展。

这里借助SPFA来写,记
f[u][val]表示以u为节点,拓展到下一个数列需要val的方案数是多少f[u][val] 表示以u为节点,拓展到下一个数列需要val的方案数是多少f[u][val]表示以u为节点,拓展到下一个数列需要val的方案数是多少

可以有转移
v为u的出点(u→v),当val=a[v]的时候,f[v][a[u]+a[v]]+=f[u][val]v为u的出点(u →v),当val = a[v]的时候,f[v][a[u] + a[v]] += f[u][val]v为u的出点(u→v),当val=a[v]的时候,f[v][a[u]+a[v]]+=f[u][val]

这里用 map<int, int> 来记录值(因为每个值的范围都是 long long,但是数量又很少),同时 建边的逻辑为 e[u][val] 表示从 u 出边,下一个值为 val 的点的集合。

注意这里不存在一个点只更新一次的情况,所以我们只用一个 st 记录某个状态是否在 heap 中,不在的话就加入,否则不加入。

不能按照 Dijkstra 的逻辑来,不然是错误的。

cpp 复制代码
void solve()
{
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1);

    for (int i = 1; i <= n; i++) cin >> a[i];

    priority_queue<PII, vector<PII>, greater<PII>> heap;
    vector<map<int, vector<int>>> e(n + 1);
    vector<map<int, int>> f(n + 1);
    map<PII, bool> st;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        e[u][a[v]].push_back(v);
        f[v][a[u] + a[v]] ++;
        if (st[make_pair(a[u] + a[v], v)] == 0) {
            st[make_pair(a[u] + a[v], v)] = 1;
            heap.push({a[u] + a[v], v});
        }
    }

    int ans = 0;
    while(heap.size()) {
        auto [ne, u] = heap.top();
        heap.pop();

        for (auto v : e[u][ne]) {
            if (st[make_pair(a[u] + a[v], v)] == 0) {
                st[make_pair(a[u] + a[v], v)] = 1;
                heap.push({a[u] + a[v], v});
            }
            (f[v][a[u] + a[v]] += f[u][ne]) %= mod;
        }
    }

    for (int i = 1; i <= n; i++) {
        for (auto [_, c] : f[i]) {
            (ans += c) %= mod;
        }
    }
    cout << ans % mod << endl;
}

F. Omega Numbers

For a given number nnn, consider the function ω(n)\omega(n)ω(n), which is equal to the number of unique prime numbers in the prime factorization of the number nnn.

For example, ω(12)=ω(22⋅3)=2\omega (12) = \omega (2^2 \cdot 3) = 2ω(12)=ω(22⋅3)=2. And ω(120)=ω(23⋅3⋅5)=3\omega (120) = \omega (2^3 \cdot 3 \cdot 5) = 3ω(120)=ω(23⋅3⋅5)=3.

For an array of natural numbers aaa and a natural number kkk, we define f⁡(a,k)=∑i<jω(ai⋅aj)k\operatorname{f}(a, k) = \sum_{i < j} \omega(a_i \cdot a_j)^kf(a,k)=∑i<jω(ai⋅aj)k for all i<ji < ji<j.

You are given an array of natural numbers aaa of length nnn and a natural number kkk. Calculate f⁡(a,k)\operatorname{f}(a, k)f(a,k) modulo 998 244 353998\,244\,353998244353.

很麻烦的一题。

首先要发现一个重要的性质,即 ω(x⋅y)=ω(x)+ω(y)−ω(gcd⁡(x,y))\omega(x \cdot y) = \omega(x) + \omega(y) - \omega(\operatorname{gcd}(x,y))ω(x⋅y)=ω(x)+ω(y)−ω(gcd(x,y))

同时,对于 [1,2∗105][1, 2*10^5][1,2∗105] 这个范围内的整数,所有数字拥有质因子的个数都不会超过 7!

发现上述性质之后,我们可以将问题转化为:
按照 gcd 分类,统计 "恰好 gcd=g 且 ω(ai)+ω(aj)=Sω(a_i)+ω(a_j)=Sω(ai)+ω(aj)=S" 的对数,然后它们的贡献就是 (S−ω(g))k×对数(S−ω(g))^k×对数(S−ω(g))k×对数。

这样按组分开计算的方式会使得复杂度大大降低,可以通过题目的时间限制。

那么怎么分组呢??

官方题解中给了如下做法:

cnt[x][len]:表示 数组中有多少个数 A,满足:A 能被 x 整除,且 ω(A) = len。

注意:

  • len 指的就是 ω(A)(不同质因子个数)。
  • cnt[x] 是对"能被 x 整除"的那些数组元素按 ω 值的分布统计。

为什么要这么统计?

因为如果 gcd(ai,aj)=ggcd(a_i, a_j) = ggcd(ai,aj)=g,那么 aia_iai 和 aja_jaj 都可以被 ggg 整除。我们先统计"都能被 g 整除的数"的分布(这就是 cnt[g]cnt[g]cnt[g]),再用这些数配对计算候选对数,最后用筛去掉 gcd 更大倍数的部分,得到 "恰好 gcd=g" 的对。

怎么统计呢?

就是直接暴力枚举,按照调和级数和质因子的个数枚举。

dp[g][sumlen]:临时的 dp[g][sumlen] 最终表示恰好 gcd(ai,aj) = g 且 ω(ai)+ω(aj) = sumlen 的 无序对数量(i < j 的对数)。

怎么得到呢??

先用 cnt[g] 计算"被 g 同时整除的数之间的所有配对数"并按 sumlen = ω(ai)+ω(aj) 累加到 dp[g][sumlen](这是包含了 gcd 可能为 g 的所有对,也包括 gcd 更大倍数的情况)。

  • 如果 len1 != len2,贡献是 cnt[g][len1] * cnt[g][len2](这些是有序配对数,实际我们只要无序对,所以在代码里枚举 len1 < len2 用乘积)。

  • 如果 len1 == len2,贡献是 cnt[g][len] * (cnt[g][len] - 1) / 2(组合数,保证 i<j)。

然后用筛法(从大到小枚举 g)减去 dp[multiple_of_g][sumlen],把那些 gcd 实际上是 2g, 3g, ... 的对去掉,剩下的就是 gcd 恰好 等于 g 的对数。

最后由公式 ω(ai * aj) = sumlen - ω(g),就可以直接分组算贡献了:

ANS += dp[g][sumlen] * pow(sumlen - ω(g), k);

大致思路如上,下面是代码:

cpp 复制代码
const i64 mod = 998244353;
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
int qpow(int a, int k) {
	a %= mod;
	i64 res = 1 % mod;
	while(k) {
		if (k & 1) res = (i64)res * a % mod;
		a = (i64)a * a % mod;
		k >>= 1;
	}
	return res;
}

int inv(int x) {
	return qpow(x, mod - 2);
}

static constexpr int MAX_N = 1e7;
std::vector<int> minp, primes;
void sieve(int n) {
    minp.assign(n + 1, 0);
    primes.clear();
    for (int i = 2; i <= n; i++) {
        if (minp[i] == 0) {
            minp[i] = i;
            primes.push_back(i);
        }
        for (auto p : primes) {
            if (i * p > n) {
                break;
            }
            minp[i * p] = p;
            if (p == minp[i]) {
                break;
            }
        }
    }
}
bool isprime(int n) {
    return minp[n] == n;
}

void solve()
{
    int n, k, Maxa;
    cin >> n >> k;
    vector<int> a(n + 1), w(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];

    Maxa = *max_element(range_(a));
    vector<vector<int>> cnt(Maxa + 1, vector<int>(10, 0));
    for (int i = 1; i <= n; i++) {
        int x = a[i];
        int c = 0;
        while(x > 1) {
            int p = minp[x];
            w[i]++;
            while (x % p == 0) x /= p;
        }
        cnt[a[i]][w[i]]++;
    }

    for (int x = 1; x <= Maxa; x++) {
        for (int i = 2; i * x <= Maxa; i++) {
            for (int len = 0; len <= 8; len++) {
                (cnt[x][len] += cnt[i * x][len]) %= mod;
            }
        }
    }
    vector<vector<int>> f(Maxa + 1, vector<int>(17, 0));
    for (int x = 1; x <= Maxa; x++) {
        for (int sumlen = 1; sumlen <= 16; sumlen++) {
            for (int len1 = 0; len1 <= 8; len1++) {
                int len2 = sumlen - len1;
                if (len2 < 0 || len2 > 8) continue;
                if (len1 > len2) continue;
                if (len1 == len2) {
                    (f[x][sumlen] += ((cnt[x][len1] * (cnt[x][len2] - 1) % mod) * inv(2) % mod)) %= mod;
                } else {
                    (f[x][sumlen] += cnt[x][len1] * cnt[x][len2] % mod) %= mod;
                }
            }
        }
    }
    for (int x = Maxa; x >= 1; x--) {
        for (int i = 2; i * x <= Maxa; i++) {
            for (int len = 0; len <= 16; len++) {
                ((f[x][len] -= f[x * i][len]) += mod) %= mod;
            }
        }
    }
    w.clear();
    for (int g = 1; g <= Maxa; g++) {
        int x = g;
        int cnt = 0;
        while(x > 1) {
            int p = minp[x];
            cnt++;
            while (x % p == 0) x /= p;
        }
        w[g] = cnt;
    }

    int ans = 0;
    for (int x = 1; x <= Maxa; x++) {
        for (int len = 1; len <= 16; len++) {
            (ans += ((qpow((len - w[x] + mod) % mod, k) % mod) * f[x][len]) % mod) %= mod;
        }
    }
    cout << ans << endl;
}

signed main()
{
    cin.tie(nullptr) -> ios::sync_with_stdio(false);
    int T = 1;
    sieve(2e5 + 10);
    cin >> T;
    while (T--) solve();
    return 0;
}
相关推荐
自学小白菜2 小时前
每周刷题 - 第三周 - 双指针专题 - 02
python·算法·leetcode
杜子不疼.3 小时前
【LeetCode76_滑动窗口】最小覆盖子串问题
算法·哈希算法
ComputerInBook3 小时前
代数基本概念理解——特征向量和特征值
人工智能·算法·机器学习·线性变换·特征值·特征向量
不能只会打代码3 小时前
力扣--3433. 统计用户被提及情况
java·算法·leetcode·力扣
biter down4 小时前
C++ 解决海量数据 TopK 问题:小根堆高效解法
c++·算法
用户6600676685394 小时前
斐波那契数列:从递归到缓存优化的极致拆解
前端·javascript·算法
初夏睡觉4 小时前
P1055 [NOIP 2008 普及组] ISBN 号码
算法·p1055
程芯带你刷C语言简单算法题4 小时前
Day28~实现strlen、strcpy、strncpy、strcat、strncat
c语言·c++·算法·c
踏浪无痕4 小时前
周末拆解:QLExpress 如何做到不编译就能执行?
后端·算法·架构