CF教育场 103 解题补题报告

官方题解](https://codeforces.com/blog/entry/87356)

A题 K-divisible Sum(贪心)

\(T(1\leq T \leq 1000)\) 组数据。

给定两个正整数 \(n,k(1\leq n,k \leq 10^9)\),尝试构造一个正整数组成的数列 \(\{a_n\}\),使得 \(\displaystyle\sum\limits_{i=1}^{n} a_i\)\(k\) 的倍数,并且使得 \(\{a_n\}\) 的最大项尽可能的小。

很显然,这个数列的和可以是一个大于等于 \(n\) 的任意值,而且显然倍数越小越好。

那么我们的做法就是:找到一个 \(k\) 的倍数中大于等于 \(n\) 的最小值,然后构造数列,使得和为这个值即可。

怎么构造:尽可能平均赋值,平均不了就只能选一些项加 \(1\) 了。

给出代码:

#include<bits/stdc++.h>
using namespace std;
int n, k;
void solve()
{
    scanf("%d%d", &n, &k);
    int K = k;
    //感谢这题,贡献了我cf生涯的第一次Hacked:循环加(被注释掉的那行)是会被特殊数据T掉的
    //(我当时也想到了,鬼知道我当时怎么认为不会T的)
    //while (K < n) K += k;
    if (K < n) K = (n % k == 0) ? n : (n/k + 1) * k;
    if (K % n == 0) printf("%d\n", K / n);
    else printf("%d\n", K / n + 1);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}

B题 Inflation(贪心)

\(T(1\leq T \leq 1000)\) 组数据。

给定正整数 \(n,k(2\leq n \leq 100, 1\leq k \leq 100)\)

现在有一个物品的原价为 \(p_0(1\leq p_0 \leq 10^9)\),接下来第 \(i(1\leq i < n)\) 个月的价格变化量为 \(p_i(1 \leq p_i \leq 10^9)\)。如果某次的价格变化幅度超过了 \(k\%\),那么认为这件商品的通胀指数有亿点高。

商店老板不希望他的商品的通胀指数超标,所以希望对一部分 \(p_i(0 \leq i < n)\) 加上一些数字,使得最后通胀指数不会超标。现在我们需要求出总修改量的最小值。

这次比赛的好心情到这题开始就结束了(就离谱),然后到第二天早上得知自己被 \(Hacked\) 之后跌到了谷点(逃

用人话翻译一下,就是说,对于任意 \(1 \leq i < n\), 都有 \(\frac{p_i}{\displaystyle\sum\limits_{j=0}^{i-1} p_j} \leq \frac{k}{100}\)(好像更不像人话了)

我想了各类方法:DP,二分,图论,分数规划,数据结构维护,。。。。。

知道第二天,才发现了一个血淋淋的事实:对于 \(p_i\),只能不能!!!

这样的话,题目似乎就变得比较显然了:

如果第 \(i\) 天不符合要求,那么只能扩大前 \(i-1\) 天的和;如果第 \(i-1\) 天不符合要求,那么只能扩大前 \(i-2\) 天的和,与此同时,最好之前在扩大前 \(i-1\) 天的和的时候,扩大的不是第 \(i-1\)天......

这样,我们发现了一个铁的事实:我们只需要增加 \(p_0\)即可,并且可以保证这一定是最优解!

方法有两种,一种是二分判定,一种是从后向前递推,两个方法都能过,不过后者时间复杂度更低,而且似乎更好写

//只有思路,代码是借鉴这位大佬的
//原地址:https://www.cnblogs.com/HotPants/p/14348300.html
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, k;
long long p[N];

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &n, &k);
        for (int i = 0; i < n; ++i)
            scanf("%lld", &p[i]);
        long long sum = p[0], res = 0;
        for (int i = 1; i < n; ++i) {
            if (100 * p[i] > k * sum) {
                long long delta = (100 * p[i] - k * sum + k - 1) / k;
                res += delta;
                sum += delta;
            }
            sum += p[i];
        }
        printf("%lld\n", res);
    }
    return 0;
}

C题 Longest Simple Cycle(递推)

这题似乎并不能简单概括题意,所以还是老老实实去读题吧。

找图中的最长环?好像直接建图并且 DFS 就好,但是可能时空复杂度有点高(我暂时没有看到有人这么写

不过,这题似乎并不需要这么写:我们看到图上的环的构成方式就可以猜测,这个环无非是多个链之间相互拼接而成。

所以说,我们只需要从前往后递推就行了,如果碰上无法成环或者可以重新组成新环的情况就要随时更新。一路上记得更新最大值,最后输出即可。

(重新组成新环,可以看一下第三组样例,把第一和第三根链子互相换一下,就可以明白了)

不想写了,这题够折磨人了,直接上代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n;
long long c[N], a[N], b[N];
void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &c[i]);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &b[i]);
    long long ans = -1, tmp;
    for (int k = 2; k <= n; ++k) {
        if (a[k] == b[k] || k == 2) {
            tmp = abs(a[k] - b[k]) + 2;
            ans = max(ans, tmp + c[k] - 1);
        }
        else {
            if (tmp + c[k-1] - 1 -abs(a[k] - b[k]) >= abs(a[k] - b[k]))
            tmp += 2 + (c[k - 1] - 1) - abs(a[k] - b[k]);
            else tmp = 2 + abs(a[k] - b[k]);
            ans = max(ans, tmp + c[k] - 1);
        }
    }
    printf("%lld\n", ans);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}

D题 Journey(DP/图论)

\(n+1\) 个城市(\(n \leq 3*10^5\)),标号从 \(0\) 开始,用一个字符串表示每个城市的链接情况,如果第 \(i\) 位为 \(L\),表示有一条有向边从 \(i\) 连向 \(i-1\) ,反之对应。在这张图上,每从一个节点走到另外一个节点上,就会导致整个图上的所有边的方向反转一次。对于每个点,输出他能到达几个点。

这个图显然具有奇偶性。

法一:维护数组前缀

从左向右,只有 \(RLRLRL......\) 这样的形式才能一直走下去,从右向左则是反过来。

如果暴力判断,复杂度是 \(O(n^2)\) 的,很容易 \(T\)

显然,我们可以通过一些方法,将复杂度降到 \(O(n)\) (例如 \(DP\) ),但是我似乎并不是很会(逃

法二:维护图联通性

我们可以建立两个图,一个正图一个反图,正图和反图内部没有边,两个图之间互相连边(说白了就是二分图)。

二分图的构造很显然:每个点拆成两个点,然后分列左右,之间根据这个图的关系相互连边(发现其实连的就是无向边)。

对于每个城市,就相当于左边对应的点所在的联通分量的大小。

暴力枚举的复杂度是 \(O(n^2)\) 的,但是好像用 \(Tarjan\) 可以优化。我没学过 \(Tarjan\),但是这个复杂度应该可以证明是 \(O(n)\) 的。

(我失了智了,其实这玩意只需要直接 \(DFS\) 就行了(逃))

(话说 \(CodeForces\) 这种多组数据的输入方式,对于清理内存真的是折磨,我就被这玩意卡了好几次之后才 \(A\)

#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
int n;
char s[N];
//
int tot, Head[N<<1], Next[N<<2], ver[N<<2];
void addEdge(int u, int v) {
    ver[++tot] = v, Next[tot] = Head[u], Head[u] = tot;
    ver[++tot] = u, Next[tot] = Head[v], Head[v] = tot;
}
void clearGraph() {
    memset(Head, 0, sizeof(int) * 2 * (n + 1));
    memset(Next, 0 ,sizeof(int) * (tot + 1));
    memset(ver , 0 ,sizeof(int) * (tot + 1));
    tot = 0;
}
int tag, vis[N<<1], ans[N];
void dfs(int x) {
    vis[x] = tag, ans[tag]++;
    for (int i = Head[x]; i; i = Next[i]) {
        int to = ver[i];
        if (!vis[to]) dfs(to);
    }
}
void solve()
{
    //build
    scanf("%d%s", &n, s + 1);
    for (int i = 1; i <= n; ++i) {
        int u, v;
        if (s[i] == 'L') u = i, v = (i - 1) + (n + 1);
        else u = i - 1, v = i + (n + 1);
        addEdge(u, v);
    }
    //run
    tag = 0;
    for (int i = 0; i <= n; ++i) {
        if (vis[i]) continue;
        tag++;
        dfs(i);
    }
    //output
    for (int i = 0; i < n; ++i)
        printf("%d ", ans[vis[i]]);
    printf("%d\n", ans[vis[n]]);
    //clear
    clearGraph();
    memset(ans, 0, sizeof(int) * (tag + 1));
    memset(vis, 0, sizeof(int) * 2 * (n + 1));
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}

posted @ 2021-01-31 18:40  cyhforlight  阅读(80)  评论(0编辑  收藏  举报