Contestant~. Rank 538. Rating +59 (+409 -350).

A. Rudolph and Cut the Rope

题意

给定 \(n\) 个钉子,第 \(i\) 个钉子钉在离地面 \(a_i\) 的位置,并连有 \(b_i\) 长的绳子。所有钉子的另外一端连着同一个糖果。

输出需要剪断多少根绳子,让糖果掉到地上。

思路

画个图即可,我们不难发现最后的结果就是 \(a_i > b_i\) 的钉子个数。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    int ans = 0;
    for(int i=0;i<n;i++){
        int a, b;
        cin >> a >> b;
        if(a > b) ans ++;
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

具象的,抽象的,物理的

B. Rudolph and Tic-Tac-Toe

题意

\(3\) 个人玩 \(3 \times 3\) 的井字格游戏。

给定某个时刻的井字格状态,输出当前是否出现了赢家。

如果没有赢家,或者出现多个赢家,输出 \(\mathtt{DRAW}\)

思路

直接枚举即可。

时间复杂度:\(O(1)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    vector<string> s(3);
    for(int i=0;i<3;i++) cin >> s[i];
    bool a = false, b = false, c = false;
    for(int i=0;i<3;i++){
        if(s[i][0] == s[i][1] && s[i][1] == s[i][2]){
            if(s[i][0] == 'O') a = true;
            else if(s[i][0] == 'X') b = true;
            else if(s[i][0] == '+') c = true;
        }
    }
    for(int i=0;i<3;i++){
        if(s[0][i] == s[1][i] && s[1][i] == s[2][i]){
            if(s[0][i] == 'O') a = true;
            else if(s[0][i] == 'X') b = true;
            else if(s[0][i] == '+') c = true;
        }
    }
    if(s[0][0] == s[1][1] && s[1][1] == s[2][2]){
        if(s[0][0] == 'O') a = true;
        else if(s[0][0] == 'X') b = true;
        else if(s[0][0] == '+') c = true;
    }
    if(s[0][2] == s[1][1] && s[1][1] == s[2][0]){
        if(s[0][2] == 'O') a = true;
        else if(s[0][2] == 'X') b = true;
        else if(s[0][2] == '+') c = true;
    }
    if(a && !b && !c){
        cout << 'O' << '\n';
    }else if(!a && b && !c){
        cout << 'X' << '\n';
    }else if(!a && !b && c) {
        cout << '+' << '\n';
    }else cout << "DRAW" << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

主打一个复制粘贴

C. Rudolf and the Another Competition

题意

对于 \(n\) 个人和 \(m\) 道题,给定每个人每道题需要花的时间。

每个人可以任意排列做题的顺序,给定比赛的总时长,在 \(\mathtt{ICPC}\) 规则下,若每个人都希望自己的排名尽可能高,输出第一个人最后的排名。

若第一个人和其他人的排名一致,第一个人的排名更高。

\(\mathtt{ICPC}\) 规则:

  1. 每一道题的罚时 \(=\) 通过这道题离 比赛开始 的时间;

  2. 排名的主关键词:过题数(降序),总罚时(升序)

思路

对于某一个人,我们将所有过题的时间升序排序,然后依次做题,如果某一道题做完的时候已经超过比赛总时长,那这题及以后的所有题都不用做了。

以上,我们统计过题数 \(cnt\),罚时 \(pen\),每个人的下标 \(i\)

我们将三元组 \(\{-cnt, pen, i\}\) 升序排序,然后输出 \(i = 1\) 的位置即可。

不排序也是可以的,因为我们只需要知道第一个人的排名。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n, m, h;
    cin >> n >> m >> h;
    vector<ppii> res(n);
    for(int i=0;i<n;i++){
        vector<int> a(m);
        for(int j=0;j<m;j++) cin >> a[j];
        sort(all(a));
        int last = 0, cnt = 0, p = 0;
        for(int j=0;j<m;j++){
            if(last + a[j] <= h){
                p += last + a[j];
                cnt ++;
                last += a[j];
            }else break;
        }
        res[i] = {-cnt, {p, i}};
    }
    sort(all(res));
    for(int i=0;i<n;i++){
        if(res[i].sc.sc == 0){
            cout << i + 1 << '\n';
            return;
        }
    }
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

ICPCGPT

D. Rudolph and Christmas Tree

题意

给定一个三角形的底和高,在坐标系中,三角形底边和 \(x\) 轴平行,高和 \(y\) 轴平行。

给定 \(n\) 个上述三角形的底边所在的 \(y\) 坐标,输出最后坐标轴上的图形的面积。

思路

显然会有重叠。

对于两个重叠的三角形,我们不难发现,底下的三角形需要去掉一个小三角形。

小三角形和原三角形是相似的,因而计算面积是很容易的(相似比的平方即为面积比)。

也许你会觉得三个三角形重叠的时候不能按照上述操作计算,但本题中三角形大小都是一样的,画个图不难发现,上述操作移除的小三角形面积不受其他的三角形影响。

那么,我们降序排序底边 \(y\) 坐标,然后枚举相邻的三角形是否出现重叠即可。

p.s. 本题按照升序给出了 \(y\) 坐标。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    //《容斥》
    int n, d, h;
    cin >> n >> d >> h;
    vector<int> y(n);
    for(int i=0;i<n;i++) cin >> y[i];
    int pre = inf;
    double ans = 0;
    for(int i=n-1;i>=0;i--){
        ans += (double) (d * h) / 2;
        if(y[i] + h > pre){
            ans -= (double) (d * h) / 2 * ((double) (y[i] + h - pre) * (y[i] + h - pre) / h / h);
        }
        pre = y[i];
    }
    printf("%.7f\n", ans);
}

signed main(){
    //ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

还以为是不可做的几何题(x)。输出的时候记得输出 \(7\) 位小数

E1. Rudolf and Snowflakes (simple version)

题意

对于整数 \(k \in [2, +\inf)\),定义 "雪花" 构造操作如下:

  1. 定义一个点为根节点;

  2. 根节点有 \(k\) 个子节点;

  3. 上述 \(k\) 个子节点,每个子节点自己有 \(k\) 个子节点;

  4. 重复任意次操作 \(3\),操作 \(3\) 需要执行至少一次。

\(k = 4\),操作 \(3\) 执行一次,雪花图如下:

现在,给定节点总数 \(n\),判断是否可以用这些节点构造出一个雪花。

本题中,\(n \leq 1e6\)

思路

本题的 \(n\) 比较小,我们考虑直接枚举所有 \(k\),以及操作 \(3\) 执行的次数,对 \(n\) 打表即可。

不难发现 \(n\) 是等比数列的前 \(n\) 项和,我们套公式爆搜即可。

时间复杂度:不大

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

map<int, bool> mp;

int qp(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = (res * a);
        a = (a * a);
        b >>= 1;
    }
    return res;
}

void init(){
    int k = 2;
    while(true){
        bool f = false;
        int p = 3;
        while(true){
            if((qp(k, p) - 1) / (k - 1) > 1e6) break;
            mp[(qp(k, p) - 1) / (k - 1)] = true;
            f = true;
            p ++;
        }
        if(!f) break;
        k ++;
    }
}

void solve() {
    int n;
    cin >> n;
    cout << (mp.count(n) ? "YES\n" : "NO\n");
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

狠狠地打表

E2. Rudolf and Snowflakes (hard version)

题意

\(E1\) 的基础上,\(n \leq 1e18\)

思路

首先,因为我们枚举的是等比数列的前 \(n\) 项和,我们不妨固定项数,观察答案不超过 \(1e18\)\(k\) 的最大值。

我们不难发现,在量级上考虑,\(k >1e9\) 时,只能取两项,\(k \in [1e6, 1e9]\) 时,只能取三项。

我们不可以取两项,那么我们只需对取三项的情况进行单独考虑。

取三项时,方程即为 \(q ^ 2 + q + 1 = n\),那么如果能解出 \(q \geq 2\)\(n\) 就是正确的。

否则,\(k\) 一定小于 \(1e6\) 量级。

分析复杂度可知,对 \(k \in [2, 1e6]\),我们直接打表即可。

时间复杂度:还好

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

map<int, bool> mp;

__int128 qp(__int128 a, int b) {
    __int128 res = 1;
    while (b > 0) {
        if (b & 1) res = (res * a);
        a = (a * a);
        b >>= 1;
    }
    return res;
}

void init(){
    int k = 2;
    while(true){
        if(k > 1e6) break;
        bool f = false;
        int p = 3;
        while(true){
            if(p > 70) break;
            if((qp(k, p) - 1) / (k - 1) > 1e18 || (qp(k, p) - 1) / (k - 1) <= 0) break;
            mp[(qp(k, p) - 1) / (k - 1)] = true;
            f = true;
            p ++;
        }
        if(!f) break;
        k ++;
    }
}


void solve() {
    int n;
    cin >> n;
    int delta = 1 - 4 * (1 - n);
    if((int) sqrt(delta) * (int) sqrt(delta) == delta){
        if((-1 + (int) sqrt(delta)) % 2 == 0){
            int q = (-1 + (int) sqrt(delta)) / 2;
            if(q >= 2) {
                cout << "YES\n";
                return;
            }
        }
    }
    cout << (mp.count(n) ? "YES\n" : "NO\n");
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

注意 越界 / 浮点数误差 的问题!

F. Rudolph and Mimic

题意

这是一道交互题。

在长度为 \(n\) 的序列 \(a\) 中,满足 \(1 \leq a_i \leq 9\),存在一只怪物,躲藏在某一个未知元素后。

定义一次交互如下:

输出:给定序列中需要删除的元素;

输出后:

  1. 将元素删除;

  2. 打乱序列;

  3. 怪物可以选择是否将其所在元素的值改变为另一个不同的数,但两次交互后如果都不改变,第三次一定会改变

输入:操作后的序列

在不超过 \(5\) 次交互内,确定怪物在哪个下标。

注意,本题的所有下标都是针对上一次给定的序列,而不是最初给的序列。

思路

因为怪物第三次一定会改变,那么我们只要等他改变即可。

在等他改变的时候,我们为了防止误删怪物所在的元素,我们直接不删除。

那么,当他改变的时候,我们保留所有等于 他所在的元素的值 的元素,然后把其他的元素全都删掉。

接着,我们继续等待,当他再改变时,那个不同的元素就是怪物了。

为了方便判断,我们不妨记录每个值对应出现的下标,然后比较每个值出现了几次即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 3e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    auto fetch = [&]() -> vector<vector<int>>{
        vector<vector<int>> a(10);
        for(int i=1;i<=n;i++){
            int cur;
            cin >> cur;
            a[cur].pb(i);
        }
        return a;
    };
    vector<vector<int>> a0 = fetch();
    int leave;
    while(true){
        cout << "- 0\n";
        cout.flush();
        vector<vector<int>> a1 = fetch();
        int ind = -1;
        for(int i=1;i<=9;i++){
            if(a1[i].size() > a0[i].size()) ind = i;
        }
        if(ind != -1){
            leave = ind;
            a0 = a1;
            break;
        }
    }
    cout << "- ";
    vector<int> del;
    for(int i=1;i<=9;i++){
        if(i == leave) continue;
        for(auto e : a0[i]) del.pb(e);
    }
    cout << del.size() << ' ';
    n -= del.size();
    for(auto e : del) cout << e << ' ';
    cout << '\n';
    cout.flush();
    while(true){
        vector<vector<int>> a1 = fetch();
        if(a1[leave].size() == a0[leave].size()){
            cout << "- 0\n";
            cout.flush();
        }else{
            for(int i=1;i<=9;i++){
                if(i == leave) continue;
                if(a1[i].size() == 1){
                    cout << "! " << a1[i][0] << '\n';
                    cout.flush();
                    return;
                }
            }
        }
    }
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

但凡题面正常一点

G. Rudolf and CodeVid-23

题意

给定 \(n\) 个病症,以及 \(m\) 种药。

每种药可以治疗若干病症,但也会导致若干个并发症。每个药都有一段服用时间。同一时刻只能服用一种药。治疗的病症不会出现在并发症中。

现在,给定初始患上的病症,输出在使用若干药后所有病症痊愈的条件下需要服用的最短时间。

本题输入使用二进制表示某一个病症是否患上。

思路

首先,因为二进制串长度很短,所以我们直接将其转为十进制处理。

对于任意一个状态,如果我们确定需要用什么药,那么用药结束后的状态是唯一确定的。

那么,我们唯一需要做的,就是对于初始状态,通过状态的转换,从而变为 \(0\)

那么我们考虑图论,从某一个状态,连一条有向边到 使用某一个药之后的状态,边权就是这个药需要服用的时间。

那么很显然了,这道题就可以转化为 "求初始状态到 \(0\) 的最短路长度"。

有关计算,设 \(x, h, s\) 分别为服药前状态、能治哪些病、并发症,那么我们将 \(h\) 取反为 \(rh\),不难发现最后结果就是 \((x\ \&\ rh)|s\)。(取反操作可以考虑异或)。

有关建图,我们用类似 "状压枚举" 的方式,枚举所有可能的状态,对于某一个状态,枚举所有的药,然后按上述操作建边即可。

时间复杂度:\(O(2^n(m+n))?\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n, m;
    cin >> n >> m;
    int mx = (1 << n) - 1;
    auto calc = [&](string s) -> int{
        int ans = 0;
        for(auto e : s) ans = ans * 2 + (e - '0');
        return ans;
    };
    auto heal = [&](int pre, int heal, int side) -> int{
        return (pre & (heal ^ mx)) | side;
    };
    string c;
    cin >> c;
    int start = calc(c);
    vector<int> w(m), h(m), s(m);
    for(int i=0;i<m;i++) {
        string a, b;
        cin >> w[i] >> a >> b;
        h[i] = calc(a), s[i] = calc(b);
    }
    vector<vector<pii>> e(mx + 1);
    for(int p=0;p<=mx;p++){ //《状压》
        for(int i=0;i<m;i++){
            e[p].pb(heal(p, h[i], s[i]), w[i]);
        }
    }
    auto dijkstra = [&](int start, int end) -> int{
        vector<int> dist(mx + 1, inf);
        vector<bool> st(mx + 1);
        dist[start] = 0;
        priority_queue<pii, vector<pii>, greater<>> q;
        q.emplace(0, start);
        while (!q.empty()){
            auto t = q.top();
            q.pop();
            int cur = t.second, d = t.first;
            if (st[cur]) continue;
            st[cur] = true;
            for (auto [j, wi] : e[cur]){
                if (dist[j] > dist[cur] + wi){
                    dist[j] = dist[cur] + wi;
                    q.emplace(dist[j], j);
                }
            }
        }
        if (dist[end] == inf) return -1;
        return dist[end];
    };
    cout << dijkstra(start, 0) << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

怎么比 \(E2\) 简单啊