CF Round 699(Div2) 解题补题报告

官方题解

A题 Space Navigation(数学)

现在我们在坐标系上面,要从点 \((0,0)\) 到点 \((px,py)\),按照一个给定的操作序列 \(s\) 来走(例如 \(UURLLDR\),各字母分别对应上下左右)。不过,由于可能到不了终点,所以我们对其中的某些步可以选择走或者不走。问我们能否最后到终点。

\(-10^5 \leq px,py \leq 10^5,1\leq |s|\leq 10^5\)

记 \(up\) 为 \(U\) 的数量,\(down\) 为 \(D\) 的数量,\(left\) 为 \(L\) 的数量,\(right\) 为 \(R\) 的数量。

显然不管我们怎么选,最后到达的位置必然在 \((x,y)\) 上,其中 \(-left \leq x \leq right,-down \leq y \leq up\)

那我们看下终点在不在这个范围上就行了

#include <bits/stdc++.h>
using namespace std;
int px, py;
char s[100010];
bool solve()
{
    memset(s, 0, sizeof(s));
    scanf("%d%d%s", &px, &py, s);
    int up = 0, down = 0, left = 0, right = 0;
    for (int i = 0, Len = strlen(s); i < Len; ++i)
        switch (s[i]) {
            case 'U' : up++;    break;
            case 'D' : down++;  break;
            case 'L' : left++;  break;
            case 'R' : right++; break;
        }
    return (-left <= px && px <= right && -down <= py && py <= up);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
        puts(solve() ? "YES" : "NO");
    return 0;
}

B题 New Colony(模拟)

有 \(n\) 座连续的山,第 \(i\) 座山高度为 \(h_i\)。现在有个人不停从第一座山开始滚石头,如果前一座山高度比自己现在这座高,那么这块石头就会留在这,并且垫在这里(相当于所在山的高度加 \(1\)),否则就一直滚下去。当石头达到最后一座山时,石头就会滚下悬崖。现在这个人想知道, 第 \(k\) 块石头最终会停在哪(或者滚下悬崖)。

\(n\leq 100,h_i\leq 100,1\leq k \leq10^9\)

显然,当山的高度单调不增的时候,之后的石头就全掉下去了。显然,想要把山填成这样,最极端情况也就 \(9900\) 个石头,没 \(10^9\) 这么夸张。

那很显然,我们对前 \(10000\) 个石头直接硬模拟就好了,每块石头都记一下能到哪。如果掉下去了就打破循环,退出来。

这玩意复杂度的上界是 \(O(100n^2)\) 的,完全可以过,不会 \(T\)(逃

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, k, h[N];
int ans[10010];
void solve()
{
    memset(ans, 0, sizeof(ans));
    memset(h, 0, sizeof(h));
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &h[i]);
    int cnt = 0;
    while (1) {
        cnt++;
        int i;
        for (i = 1; i < n; ++i)
            if (h[i] < h[i+1]) {
                ans[cnt] = i, h[i]++;
                break;
            }
        if (i == n) break;
    }
    if (k >= cnt) printf("-1\n");
    else printf("%d\n", ans[k]);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

C题 Fence Painting

有一块篱笆,有 \(n\) 个板子,第 \(i\) 块板子的颜色为 \(a_i\)。现在我们觉得篱笆原来颜色丑的一,想要把第 \(i\) 块板子颜色涂成 \(b_i\)。

现在找来了 \(m\) 个工匠,第 \(i\) 个会在时刻 \(i\) 到来,选择一块板子并将其颜色涂成 \(c_i\),而且他肯定准时来并且一定要涂一块,不能不涂(敬业的一)。现在尝试安排一下,看看能不能如愿以偿的刷成希望的样子:能就输出方案,不能就输出 \(NO\)

\(1\leq n,m \leq 10^5,1 \leq a_i,b_i,c_i \leq n\)

显然,我们要对每一组 \(a_i\) 和 \(b_i\) 判断一下是不是一样,一样就暂时不管,否则就加入 \(ToDo\) 清单里面,准备让工匠来处理下。如果发现有的板子的颜色,工匠没法处理,那显然就无解了。

另外一个难缠的点在于,有些工匠明明不需要,但是这些人非得来刷一下,多花钱就算了,还可能会把好好的篱笆给刷坏掉,就离谱。

我们考虑一下最后一个工匠:

  • 如果前 \(m\) 个工匠已经把篱笆刷好了,那么这个工匠刷墙的颜色必须和某块木板的颜色相同,否则就会破坏掉整个篱笆
  • 如果前 \(m-1\) 个工匠没有完全把篱笆刷好,那么这个工匠刷墙的颜色必须和最后一块没刷好的木板的目标颜色相同,否则就无法达成预想目标

话说如果是第二类,那么我们想到一个策略:前面没用的工匠可以都来刷最后一块剩下的墙,然后让最后一个工匠来处理好这块墙就行了

如果是第一类,那么也有一种策略:选好这块相同颜色的板子,然后前面没用的工匠也全部安排来刷这块板子就好了

那么最后一个工匠的策略就很简单了:找到一个目标颜色和其刷的颜色相同的板子,然后在其来的时候刷这块板子。对于前面没啥用的工匠,就来刷这块板子(反正最后一个工匠会把他刷成正确的颜色)

对了,如果最后一个工匠有多种选择,要优先从 \(a_i \not= b_i\) 的板子里面选(这玩意就不多解释了,懂得都懂)

至于如何存储每个工匠可以刷什么板子,我用了邻接表的方式,开了 \(n\) 个邻接表,对应不同颜色,然后每个颜色的邻接表插入对应颜色板子的序号。

详情请见代码(比赛的时候有这个思路,但是被代码实现逼疯了):

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m, a[N], b[N], c[N], ans[N];
int tot, Head[N], Next[N<<1], ver[N<<1];
void add(int color, int num) {
    ver[++tot] = num, Next[tot] = Head[color], Head[color] = tot;
}
void solve()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &b[i]);
    for (int i = 1; i <= m; ++i)
        scanf("%d", &c[i]);
    memset(ans , 0, sizeof(int) * ( m  + 1));
    memset(Next, 0, sizeof(int) * (tot + 1));
    memset(ver,  0, sizeof(int) * (tot + 1));
    memset(Head, 0, sizeof(int) * ( n  + 1));
    tot = 0;

    for (int i = 1; i <= n; ++i)
        if (a[i] != b[i] && b[i] == c[m]) {
            a[i] = b[i] = c[m];
            ans[m] = i;
            break;
        }
    if (!ans[m]) {
        for (int i = 1; i <= n; ++i)
            if (b[i] == c[m]) {
                a[i] = b[i] = c[m];
                ans[m] = i;
                break;
            }
    }
    if (!ans[m]) { puts("NO"); return; }

    for (int i = 1; i <= n; ++i)
        if (a[i] != b[i]) add(b[i], i);

    int cnt = tot;
    for (int i = m - 1; i >= 1; i--) {
        if (Head[c[i]]) {
            ans[i] = ver[Head[c[i]]];
            Head[c[i]] = Next[Head[c[i]]];
            cnt--;
        }
        else ans[i] = ans[m];
    }
    if (cnt) { puts("NO"); return; }
    puts("YES");
    for (int i = 1; i < m; ++i)
        printf("%d ", ans[i]);
    printf("%d\n", ans[m]);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

D题 AB Graph

给定一个完全有向图,共 \(n\) 个点,\(n(n-1)\) 条边(两节点之间的边算两个不同的有向边),每个边都有一个字符 \(a\) 或者 \(b\)。

现在我们想要找出一条长度为 \(m\) 的路,沿着路上字符组成的字符串是回文的,问能否找出?能找出的话,要找出一条并打印出来。

\(2\leq n \leq 1000, m \leq 10^5\)

如果能找到两个点 \(x,y\),使得 \(x\) 到 \(y\) 边上和 \(y\) 到 \(x\) 边上的字符一样,那就太好了:沿着 \(x\) 和 \(y\) 来回走就好了。

反之,那么整个图上面,两个节点之间来回的字符都不一样。

如果 \(m\) 是奇数,那么有一个不太显然的解法:还是沿着两个点来回走,肯定是 \(ababab.....\) 这样的形式,而其长度\(m\) 是奇数的时候,显然是一个回文串(逃

\(m\) 是偶数的时候似乎就有点离谱了:建议直接看官方题解,我就不写了(逃

上一篇:2021.5.11(cf)


下一篇:CF-B. Ternary String (“ 高级做法 ”---vector+pair)很舒服~~