【题解】AtCoder Beginner Contest 448 A~F

【题解】AtCoder Beginner Contest 448 A~F

A - chmin

思路

简单模拟

Code

#include <iostream>
using namespace std;

const int N = 128;
int a[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, x;
    cin>>n>>x;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        if(x > a[i]) {
            x = a[i];
            cout<<"1"<<endl;
        } else {
            cout<<"0"<<endl;
        }
    }
}

B - Pepper Addiction

思路

贪心,胡椒能加就加,统计累积即可。

Code

#include <iostream>
using namespace std;
const int N = 1024;
int a[N], b[N], c[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, m;
    cin>>n>>m;

    long long tot = 0;

    for(int i=1;i<=m;i++) {
        cin>>c[i];
    }
    for(int i=1;i<=n;i++) {
        cin>>a[i]>>b[i];
        int g = min(c[a[i]], b[i]);
        c[a[i]] -= g;
        tot += g;
    }

    cout<<tot<<endl;
}

C

思路

由于 \(K\) 很小,可以直接模拟。

开始时存开一个数组 \(a\) 存球序号-球面数字的映射, \(a[i]\) 表示第 \(i\) 个球表面的数字,然后直接开一个字典 \(s\) 对球面数字计数, 即当前数字为 \(i\) 的球有 \(s[i]\) 个,然后把所有球的数字存进去,对于每组询问,暴力修改 \(s\) ,顺序枚举直到第一个 \(i\) 使得 \(s[i] \neq 0\) 便是本组询问的答案。

Code

#include <iostream>
#include <map>
using namespace std;

const int N = 300005;
const int K = 10;

map<int, int> s;

int a[N];
int r[K];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, q;
    cin>>n>>q;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        s[a[i]]++;
    }

    while(q--) {
        int k;
        cin>>k;
        for(int i=1;i<=k;i++) {
            cin>>r[i];
            s[a[r[i]]]--;
        }

        for(auto &p: s) {
            if(p.second) {
                cout<<p.first<<endl;
                break;
            }
        }

        for(int i=1;i<=k;i++) {
            s[a[r[i]]]++;
        }
    }
}

D - Integer-duplicated Path

思路

按照题意,是一棵树,所有待求的答案均以 \(1\) 为起点,故不难想到以 \(1\) 作为根。

开一个哈希表 \(s\) 记录当前颜色计数, 然后深搜即可,令当前节点为 \(u\)\(s[i]\) 表示 \(1\)\(u\) 的简单路径上颜色 \(i\) 的节点数量。

每到一个新的节点,如果 \(s[node[i].c]\) 不为零,表示 \(1\)\(u\) 的父节点的简单路径中,已经存在颜色为 \(node[i].c\) 的节点, \(u\) 及其子树的答案均为 Yes ;否则修改 \(s[node[i].c]++\) 对当前节点进行计数,回溯时记得恢复。

Code

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

const int N = 200005;

class Node {
public:
    vector<int > oe;
    int c;
};

Node node[N];

unordered_map<int, int> s;

bool ans[N];

void dfs(int u, int fa, bool flag) {
    if(s[node[u].c] || flag) {
        ans[u] = true;
        flag = true;
    }
    s[node[u].c]++;
    for(int v:node[u].oe) {
        if(v != fa) {
            dfs(v,u,flag);
        }
    }
    s[node[u].c]--;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n;
    cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>node[i].c;
    }
    for(int i=1;i<n;i++) {
        int u, v;
        cin>>u>>v;
        node[u].oe.push_back(v);
        node[v].oe.push_back(u);
    }
    dfs(1, 0, false);
    for(int i=1;i<=n;i++) {
        if(ans[i]) {
            cout<<"Yes"<<endl;
        } else {
            cout<<"No"<<endl;
        }
    }
}

E - Simple Division

思路

矩阵乘法+快速幂+一点点数论

假设不需要对10007取模

很显然,只是要求你构造一个大数,整除 \(M\) 即是答案。

然而如何构造这个大数?

我们可以线性模拟,假设当前的大数为 \(x\) ,重复执行 \(l_i\)\(x = x \times 10 + c_i\) ,如此递推就行,然而这个复杂度 \(O(\Sigma l_i)\) 显然不合理,考虑优化。

注意到重复执行 \(x = x \times 10 + c_i\) 是纯线性操作,可以用矩阵乘法+快速幂来解决,即对于第 \(i\) 行,需要添加 \(l_i\)\(c_i\),我们可以如此构造:

\[A_{j-1} = \left [ \begin{array}{l} r_{j-1} & 1 \\ 0 & 0 \end{array} \right] \]

\[B_i = \left [ \begin{array}{l} 10 & 0 \\ c_i & 1 \end{array} \right] \]

\[A_j = A_{j-1} \cdot B_i \]

其中 \(j\) 表示从前往后构造的第 \(j\) 位,\(r_j\) 表示现在已经构造了 \(j\) 步的大数,我们就通过一步矩阵乘法往前构造了一位,于是最终构造的大数可以用如下方式计算:

\[A_0 = A_{j-1} = \left [ \begin{array}{l} 0 & 1 \\ 0 & 0 \end{array} \right] \]

\[A = A_0 \cdot \prod \limits_{i=1}^n B_i^{l_i} \]

这时优化就显然了, \(B_i^{l_i}\) 可以用矩阵快速幂计算。

可是我们需要对10007取模

\(M \neq 10007\) 时如果我们在矩阵乘法中使用 10007 作为模数,则 \(\lfloor N/M \rfloor\) 的结果就会出错, 如果我们用 \(M\) 作为模数,则最后对 10007 取模的结果不正确

注意到

\[a\%b = a\%(kb)\%b \]

\[\begin{align} \lfloor N/M \rfloor \%10007 & = \frac{N - N \% M}{M} \% 10007 \\ & = (N - N \% M) \cdot M^{-1} \% 10007 \\ & = ((N \% 10007M) - (N \% 10007M) \% M) \cdot M^{-1} \% 10007 \\ \end{align} \]

综上,我们可以发现解决办法,在矩阵乘法中使用一个大模数 \(10007M\) (实际上可以是 10007 与 \(M\) 的最小公倍数)即可避免上述错误,于是本题就做完了。

Code

#include <iostream>
using namespace std;
typedef long long int64;
const int64 MODER = 10007;
const int64 N = 2;
int64 moder;
class Matrix{
public:
    int64 arr[N][N];

    Matrix operator*(const Matrix &o) {
        Matrix res = {0};

        for(int64 i=0;i<N;i++) {
            for(int64 j=0;j<N;j++) {
                for(int64 k=0;k<N;k++) {
                    res.arr[i][j] += arr[i][k]*o.arr[k][j]%moder;
                }
                res.arr[i][j] %= moder;
            }
        }

        return res;
    }
};
Matrix A, B;
Matrix E = {
        1, 0,
        0, 1,
};

Matrix quickPow(Matrix a, int64 x) {
    if(x==0) {
        return E;
    } else if(x == 1) {
        return a;
    } else {
        Matrix t = quickPow(a, x>>1);
        Matrix ans = t*t;
        if(x&1) {
            ans = ans*a;
        }
        return ans;
    }
}

int64 quickPow(int64 a, int64 x) {
    if(x==0) {
        return 1;
    } else if(x == 1) {
        return a;
    } else {
        int64 t = quickPow(a, x>>1);
        int64 ans = t*t%MODER;
        if(x&1) {
            ans = ans*a%MODER;
        }
        return ans;
    }
}

int main() {
    int64 k, m;
    cin>>k>>m;
    moder = (long long)m * MODER;

    A.arr[0][0] = 0;
    A.arr[0][1] = 1;
    A.arr[1][0] = 0;
    A.arr[1][1] = 0;

    while(k--) {
        int64 c, l;
        cin>>c>>l;
        B.arr[0][0] = 10;
        B.arr[0][1] = 0;
        B.arr[1][0] = c;
        B.arr[1][1] = 1;
        A=A*quickPow(B, l);
    }

    int64 r = A.arr[0][0];
    int64 inv = quickPow(m, MODER-2);
    int64 ans= ((r-r%m)*inv)%MODER;
    cout<<ans<<endl;
}

F - Authentic Traveling Salesman Problem

思路

看上去非常难,实际上非常简单。

一个朴素的想法就是y轴优先,大概是这样:

image

然而很显然, 这个只需要交替给 \(y=0\)\(y=Y\) 就很容易卡掉,非常朴素的,我们可以考虑分块,假如我们把 \(Y\) 方向分为 \(L\) 块,就会变成这样:

image

理论上最坏的路径长度为:\((Y/L) \cdot N + 2 \cdot L \cdot X\)

只需要满足以下不等式进行分块即满足要求 (从点1到原点,返回1点等小量偏差忽略不计):

\[(Y/L) \cdot N + 2 \cdot L \cdot X \leq 10^{10} \]

简单移项,就是一个高一学生就该会的解一元二次不等式,可是如果真的这么求会发现,WOC,不等式无解!

难道说要用某些奇妙的构造大法吗?

事实上并不需要,我们注意到,当我们换行时,其实并不一定需要从左侧开始,我们可以奇偶行交替来回,这样就可以省下大约一半的 \(X\) 方向的行程:

image

此时的最坏路径长度: \((Y/L) \cdot N + L \cdot X\)

求解不等式:

\[(Y/L) \cdot N + L \cdot X \leq 10^{10} \]

我并没有真的解,反正是有解的,当 \(L=250\) 左右取到最优,足够 cover 某些小量带来的额外行程,故在\(Y\)方向分250块,按照关键字\((X, Y)\)优先,枚举一遍即可。

Code

#include <iostream>
#include <set>
using namespace std;
typedef long long int64;
const int L = 250;

class Node {
public:
    int x, y, idx;
    bool operator<(const Node &o) const{
        if(x != o.x) {
            return x<o.x;
        } else {
            return y<o.y;
        }
    }
};

set<Node> s[255];

int ans[60005];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n;
    cin>>n;

    int x, y;

    int B = 20000000/L;
    cin>>x>>y;
    for(int i=2;i<=n;i++) {
        cin>>x>>y;
        s[y/B].insert({
            x, y, i
        });
    }

    cout<<"1 ";
    for(int i=0;i<=L;i++) {
        if(i&1) {
            for(auto it = s[i].rbegin(); it!=s[i].rend();it++) {
                cout<<it->idx<<" ";
            }
        } else {
            for(auto& node: s[i]) {
                cout<<node.idx<<" ";
            }
        }
    }
}

作者在赛时 XY 都分块但是没有处理好,导致卡一个点痛失一题

posted @ 2026-03-08 15:36  MistryNihilityn  阅读(13)  评论(0)    收藏  举报