Codeforces Round#699 (Div. 2)

后续更新请关注

暂时还没有 AK,留坑

A. Space Navigation

大致题意

给你一段在棋盘上的移动指令,问能否通过在不改变原指令顺序,仅删除部分或者全部或者不删除的情况下,到达某个指定地点

分析

可以通过指令直接得出可以到达的范围,比如删除所有的“向左”指令后,能够到达的最右,同理可以得到四个方向的极值,最后判断目标地点是否在极值范围内即可

AC code

#include <bits/stdc++.h>

using namespace std;

int main() {
    int _;
    cin >> _;
    for (int ts = 0; ts < _; ++ts) {
        int x, y;
        string str;
        cin >> x >> y >> str;
        int cnt[4] = {0, 0, 0, 0};
        for (auto &item : str) {
            switch (item) {
                case 'R':
                    cnt[0]++;
                    break;
                case 'L':
                    cnt[1]++;
                    break;
                case 'U':
                    cnt[2]++;
                    break;
                case 'D':
                    cnt[3]++;
                    break;
            }
        }
        if (-cnt[1] <= x && x <= cnt[0] && -cnt[3] <= y && y <= cnt[2]) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
}

B. New Colony

大致题意

给你一段台阶,然后你从第一阶开始往下面滚砖块,砖块遇到下坡就走到下一阶,遇到上坡就停止,并增加这一阶的高度。给你 n n n 个砖块,请问最后一块砖块停止在哪里

分析

首先这道题如果不能使用暴力的话,是很麻烦的一道题。但是注意观察这道题的数据范围, 1 ≤ n ≤ 100 , 1 ≤ k ≤ 1 0 9 , 1 ≤ h i ≤ 100 , ∑ n ≤ 100 1 \leq n \leq 100, 1 \leq k \leq 10^9, 1 \leq h_i \leq 100, \sum n \leq 100 1n100,1k109,1hi100,n100

假如阶梯是一直下坡或者平坡,那么砖块就会直接掉出阶梯。而如果砖块没有掉出阶梯,那么它一定会不会处于整个阶梯的最高处。所以考虑一个可能,即不断的滚落的方块将整个阶梯都填满成平地,则最多需要 n × h i = 10000 n \times h_i = 10000 n×hi=10000 个砖块

也就是说,当 k > 10000 k > 10000 k>10000 时,则最后一块砖块必然掉出阶梯。那么当 k ≤ 10000 k \leq 10000 k10000 时,暴力求解即可

AC code

#include <bits/stdc++.h>

using namespace std;

#define int long long

int main() {
    int _;
    cin >> _;
    for (int ts = 0; ts < _; ++ts) {
        int n, k;
        cin >> n >> k;
        vector<int> data(n);
        for (int i = 0; i < n; ++i) cin >> data[i];
        if (k > 10000) cout << -1 << endl;
        else {
            int last = -1;
            for (int i = 0; i < k; ++i) {
                int cur = 0;
                while (cur < n - 1 && data[cur + 1] <= data[cur]) cur++;
                if (cur == n - 1) last = -1;
                else {
                    data[cur]++;
                    last = cur + 1;
                }
            }
            cout << last << endl;
        }
    }
}

C. Fence Painting

大致题意

一个篱笆有 n n n 个木条组成,每个木条有一个初始的颜色。每一个木条都有一个指定的目标颜色。有 m m m 个油漆工,每个油漆工都只携带一种颜料,按照顺序来给这个篱笆上色,他们每次必须选择一块且恰好一块木板,把它的颜色变成油漆工所携带的颜色。问每个油漆工应该选择哪一块,使得最后的木条都变成指定的颜色

分析

首先,肯定找出目前没有达到目标的木条,然后找到合适的油漆工给它上色。如果找不到合适的油漆工,那么肯定没戏

接下来是剩下的多出来的没有使用的油漆工,也需要为他们找一块木板去上色。

注意到,由于后来的可以覆盖前面的颜色,所以可以直接考虑最后那个油漆匠。

如果根据上面的操作,这个油漆匠已经有了一块木板去上色的话,那么剩下多出来的油漆工可以都给这块木板上色,这样的话,无论最后上色是什么,最后一个油漆匠一定能把这块木板染回指定的颜色。

而如果最后一个油漆工也在那些没有使用的油漆工内,那么他只需要找到一块最终颜色和自己所携带的颜色相同的,然后指定他去给这块木板上色。剩下的那些没有使用的油漆工也去给这块木板上色即可。

AC code

#include <bits/stdc++.h>

using namespace std;

void solve() {
    int _;
    cin >> _;
    for (int ts = 0; ts < _; ++ts) {
        int n, m;
        cin >> n >> m;
        vector<int> a(n), b(n), c(m), res(m, 0);
        map<int, vector<int>> data;
        for (int i = 0; i < n; ++i) cin >> a[i];
        for (int i = 0; i < n; ++i) {
            cin >> b[i];
            if (a[i] != b[i]) data[b[i]].push_back(i + 1);
        }
        for (int i = 0; i < m; ++i) cin >> c[i];
        for (int i = 0; i < m; ++i) {
            auto iter = data.find(c[i]);
            if (iter == data.end()) continue;
            res[i] = iter->second.back();
            iter->second.pop_back();
            if (iter->second.size() == 0) data.erase(iter);
        }
        if (res.back() == 0) for (int i = 0; i < n; ++i) if (c.back() == b[i]) res.back() = i + 1;
        for (auto &item : res) if (item == 0) item = res.back();
        if (res.back() == 0 || !data.empty()) cout << "NO" << endl;
        else {
            cout << "YES" << endl;
            for (int i = 0; i < res.size(); ++i) cout << res[i] << " \n"[i == res.size() - 1];
        }
    }
}

D. AB Graph

大致题意

给你一个完全有向图,每条边都有一个字母标记,标记的字母只有可能是 “a” 或者 “b”。你需要找出一条可以包含重复点重复边的路径,满足这条路径的长度等于给定的要求,并且这条路径组成的字符串为回文串

分析

首先,由于可以有重复点和重复边,那么考虑下面的情况

如果这个图只有两个点。

此时只有两条边。如果这两条边恰好字母还相同(例如下面的 mermaid 图),那么任意长度的路径都可以画出来,只需要在这两个点之间来回走即可

a
a
A
B

如果这两条边的字母还不相同(例如下面的 mermaid 图),那么来回的路径为奇数的时候,可以画出来,只需要在这两个点之间来回走即可

a
b
A
B

如果这个图至少有三个点

取出整个图中任意三个点,假定这三个点为 a , b , c a, b, c a,b,c,这里定义 g ( a , b ) g(a, b) g(a,b) 为从点 a a a 到点 b b b 的路径的标记字母。那么我们可以知道,一定满足 g ( a , b ) = g ( b , c ) g(a, b) = g(b, c) g(a,b)=g(b,c) g ( b , c ) = g ( c , a ) g(b, c) = g(c, a) g(b,c)=g(c,a) g ( c , a ) = g ( a , b ) g(c, a) = g(a, b) g(c,a)=g(a,b) 其中至少一个

证明
反证法,假设上述不成立,则
由上述得 g ( a , b ) ≠ g ( b , c ) g(a, b) \not = g(b, c) g(a,b)=g(b,c),那么 g ( a , b ) g(a, b) g(a,b) g ( b , c ) g(b, c) g(b,c) 相异。
由上述得 g ( b , c ) ≠ g ( c , a ) g(b, c) \not = g(c, a) g(b,c)=g(c,a),那么 g ( b , c ) g(b, c) g(b,c) g ( c , a ) g(c, a) g(c,a) 相异。由于 g ( a , b ) g(a, b) g(a,b) g ( b , c ) g(b, c) g(b,c) 相异,而所有边只有两种可能,所以 g ( c , a ) g(c, a) g(c,a) g ( b , c ) g(b, c) g(b,c) 必定相同
由上述得 g ( c , a ) ≠ g ( a , b ) g(c, a) \not = g(a, b) g(c,a)=g(a,b),那么 g ( c , a ) g(c, a) g(c,a) g ( a , b ) g(a, b) g(a,b) 相异,这与上面的结论相反,所以假设不成立,即,上述结论成立

这时,通过交换 a , b , c a, b, c a,b,c 三个点,使得 g ( a , b ) = g ( b , c ) g(a, b) = g(b, c) g(a,b)=g(b,c) 得到满足,则可以得到下图

x
x
y
a
b
c

这里,我们不需要关心 y y y 的情况,无论它是否与 x x x 相同

这里,只需要进行 a → b → c → a a \rightarrow b \rightarrow c \rightarrow a abca 这样的循环,即可实现一个回文串。只需要确定是从哪一个节点开始循环即可

  • 若从 a a a 开始循环,则得到的串为 x x y x x y x x y x x … xxyxxyxxyxx \dots xxyxxyxxyxx 当长度为 3 t + 2 3t + 2 3t+2 时为回文串
  • 若从 b b b 开始循环,则得到的串为 x y x x y x x y x x y x … xyxxyxxyxxyx \dots xyxxyxxyxxyx 当长度为 3 t 3t 3t 时为回文串
  • 若从 c c c 开始循环,则得到的串为 y x x y x x y x x y x x y … yxxyxxyxxyxxy \dots yxxyxxyxxyxxy 当长度为 3 t + 1 3t + 1 3t+1 时为回文串

所以只需要判断所需要的字符串长度与 3 3 3 取模即可

AC code

#include <bits/stdc++.h>

using namespace std;

int main() {
    int _;
    cin >> _;
    for (int ts = 0; ts < _; ++ts) {
        int n, m;
        cin >> n >> m;
        vector<string> g(n);
        for (auto &item : g) item.reserve(n);
        for (int i = 0; i < n; ++i) cin >> g[i];

        if (n == 2) {
            if (g[0][1] == g[1][0] || m % 2 == 1) {
                cout << "YES" << endl;
                for (int i = 0; i < m + 1; ++i) cout << (i % 2) + 1 << " \n"[i == m];
            } else {
                cout << "NO" << endl;
            }
        } else {
            vector<int> tmp(3);
            for (int i = 0; i < 3; ++i) tmp[i] = i;
            do {
                if (g[tmp[0]][tmp[1]] == g[tmp[1]][tmp[2]]) break;
            } while (next_permutation(tmp.begin(), tmp.end()));

            assert(g[tmp[0]][tmp[1]] == g[tmp[1]][tmp[2]]);
            int cur = (m + 1) % 3;

            cout << "YES" << endl;
            for (int i = 0; i < m + 1; ++i) cout << tmp[(i + cur) % 3] + 1 << " \n"[i == m];
        }
    }
}

E. Sorting Books

大致题意

给你一段数列,进行如下的操作使得整个序列中相同的数字处于连续的子段中

操作:将这个数字放到整个字符串的最后

问,最少需要进行多少次操作

分析

首先,如果进行 n n n 次操作,那么必然可以使得整个序列满足上述的要求。因为只需要按照排序后的顺序来移动所有的数字,则必定可以满足条件

那么接下来就是想办法减少次数。

如果我选出一个值,当数字与这个值相同的时候,不对其进行操作。由于其他值被移动到了最后。所以与这个值相同的值都自然而然的被移动到了最前面,例如下面这个序列

1 , 3 , 2 , 2 , 1 , 3 1, 3, 2, 2, 1, 3 1,3,2,2,1,3

如果我选择 3 3 3 作为固定值,那么当 1 , 2 1, 2 1,2 都进行过移动后, 3 3 3 自然而然的也就满足了条件

再进一步,如果稍微修改一下这个序列,将其改为

1 , 3 , 1 , 2 , 3 , 2 1, 3, 1, 2, 3, 2 1,3,1,2,3,2

这时,发现当我仅对 3 3 3 进行移动之后, 1 , 2 1, 2 1,2 自然而然的也满足条件了。由此,可以得到一个这样的推广结论:

在某一个子区间内,如果这个子区间内存在某一个数值 x x x,满足整个区间的所有 x x x 都在这个子区间内,那么,这个 x x x 可以固定不动,且这个子区间内的其他所有值都需要进行操作。

所以此时可以进行 dp 求解,方法如下:

  • 找出所有值所对应的最小子区间
  • 假如这个子区间内选择这个值作为固定值,那么它所带来的代价就是这个子区间的长度减去这个值的个数
  • 由于需要进行减法比较麻烦,可以改变思路用加法,即求出有多少个值是可以固定不操作的
  • 则对于每一个区间的固定值,那么其带来的固定值数量就是这个区间内的值的个数
  • 对每一位进行 dp 求解即可

AC code

#include <bits/stdc++.h>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> data(n), l(n + 1, n), r(n + 1), dp(n + 1), cnt(n + 1);
    for (int i = 0; i < n; ++i) {
        cin >> data[i];
        l[data[i]] = min(l[data[i]], i);
        r[data[i]] = max(r[data[i]], i);
    }
    for (int i = n - 1; i >= 0; --i) {
        cnt[data[i]]++;
        dp[i] = max(dp[i + 1], cnt[data[i]]);
        if (l[data[i]] == i) dp[i] = max(dp[i], cnt[data[i]] + dp[r[data[i]] + 1]);
    }
    cout << n - dp[0] << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值