C++ 高质量小测题

高质量小测题,值得一做
做题的时候,一定要认真读题,不要像这个人一样

A. Coprime

题面

N N N 个整数,第 i i i 个数为 A i A_i Ai

当对于所有 1 ≤ i < j ≤ N 1 \leq i < j \leq N 1i<jN ,都有 G C D ( A i , A j ) GCD(A_i,A_j) GCD(Ai,Aj) 时,称 { A i } \{A_i\} {Ai}pairwise coprime(两两互质)的。

{ A i } \{A_i\} {Ai} 不是 pairwise coprime,但 G C D ( A 1 , … , A N ) = 1 GCD(A_1, \ldots, A_N) = 1 GCD(A1,,AN)=1 时,称 { A i } \{A_i\} {Ai}setwise coprime(集合互质)的。

请判断 { A i } \{A_i\} {Ai}pairwise coprimesetwise coprime,还是都不是。

其中 G C D ( … ) GCD(\ldots) GCD() 表示最大公约数。

思路

很明显,这道题暴力做肯定会超时。所以我们有如下思路:

计算整体 GCD:遍历所有数,计算它们的整体 GCD。

检查两两互质:统计每个质数在数组中出现的次数。如果所有质数的出现次数都不超过 1,则说明两两互质。

判断结果

  1. 如果两两互质,输出 pairwise coprime
  2. 如果不是两两互质但整体 GCD 为 1,输出 setwise coprime
  3. 否则,输出 not coprime

现在,我们就得到了一个满分思路,我可真强

由于代码过于简单,就不做过多说明了

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int spf[N];
int prime_cnt[N],a[N],n,overall_gcd;
void sieve(){
    for(int i=0;i<N;i++) spf[i]=i;
    for(int i=2;i*i<N;i++)
        if(spf[i]==i)
            for(int j=i*i;j<N;j+=i)
                if(spf[j]==j) spf[j]=i;
}
vector<int> get_primes(int x){
    vector<int> primes;
    while(x>1){
        int p=spf[x];
        primes.push_back(p);
        while(x%p==0) x/=p;
    }
    return primes;
}
int main(){
    sieve();
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    overall_gcd=a[1];
    for(int i=2;i<=n;i++) overall_gcd=__gcd(overall_gcd,a[i]);
    bool is_pairwise=true;
    for(int i=1;i<=n;i++){
        vector<int> primes=get_primes(a[i]);
        for(int p:primes){
            if(prime_cnt[p]>=1) is_pairwise=false;
            prime_cnt[p]++;
        }
    }
    if(is_pairwise) cout<<"pairwise coprime";
    else if(overall_gcd==1) cout<<"setwise coprime";
    else cout<<"not coprime";
    return 0;
}

B. 旅游

题面

小杨准备前往 B 国旅游。

B 国有 n n n 座城市,这 n n n 座城市依次以 1 1 1 n n n 编号。城市之间由 n − 1 n-1 n1 条双向道路连接,任意两座城市之间均可达(即任意两座城市之间存在可达的路径)。

小杨可以通过双向道路在城市之间移动,通过一条双向道路需要 1 1 1 单位时间。

B 国城市中有 k k k 座城市设有传送门。设有传送门的城市的编号依次为 b 1 , b 2 , ⋯   , b k b_1,b_2, \cdots ,b_k b1,b2,,bk。小杨可以从任意一座设有传送门的城市花费 0 0 0 单位时间前往另一座设有传送门的城市。

注:如果两座设有传送门的城市之间存在双向道路,那么小杨可以选择通过双向道路移动,也可以选择通过传送门传送。

小杨计划在 B 国旅游 q q q 次。第 i i i 次旅游( 1 ≤ i ≤ q 1 \le i \le q 1iq),?杨计划从编号为 u i u_i ui 的城市前往编号为 v i v_i vi 的城市,小杨希望你能求出所需要的最短时间。

思路

比较明显的 LCA 模板(不懂的可以上我的博客,里面详细介绍了关于LCA

具体思路:

  1. 树的表示:使用邻接表来存储树结构。
  2. LCA预处理:使用二进制提升法预处理每个节点的祖先信息,以便快速查询任意两点的LCA。
  3. 计算树上距离:对于任意两点u和v,它们之间的距离可以通过公式 depth[u] + depth[v] - 2 * depth[lca(u, v)] 计算。
  4. 传送门处理
    • 使用多源BFS预处理每个节点到最近的传送门节点的距离。
    • 对于每次查询,最短路径可能是直接走树上的路径,或者通过最近的传送门进行传送。
  5. 查询处理:对于每个查询(u, v),计算以下两种情况的最小值:
    • 直接走树上的路径:distance(u, v)
    • 通过传送门:distance(u, nearest_portal[u]) + distance(nearest_portal[v], v)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;      // 最大节点数
const int L=20;         // LCA的二进制提升深度
// 图的邻接表存储
int g[N], to[N*2], nxt[N*2], cnt;
// 节点信息
int d[N];               // 节点深度
int f[N][L];            // 二进制提升表
int np[N];              // 是否是传送门
int dis[N];             // 到最近传送门的距离
int near[N];            // 最近传送门节点
// BFS队列
int q[N], hd, tl;       // 队列和头尾指针
// 传送门列表
int pt[N], k;           // 传送门数组和数量
// 添加边
void add(int u, int v) {
    to[++cnt] = v;
    nxt[cnt] = g[u];
    g[u] = cnt;
}
// DFS预处理LCA
void dfs(int u, int fa) {
    f[u][0] = fa;       // 直接父节点
    // 二进制提升预处理
    for(int i=1; i<L; ++i)
        f[u][i] = f[f[u][i-1]][i-1];
    // 遍历子节点
    for(int i=g[u]; i; i=nxt[i]) {
        int v = to[i];
        if(v != fa) {
            d[v] = d[u] + 1;  // 计算深度
            dfs(v, u);
        }
    }
}
// 求LCA
int lca(int u, int v) {
    // 使u成为较深的节点
    if(d[u] < d[v]) swap(u, v);
    // u向上跳到与v同深度
    for(int i=L-1; i>=0; --i)
        if(d[u] - (1<<i) >= d[v])
            u = f[u][i];
    if(u == v) return u;
    // 同时向上跳
    for(int i=L-1; i>=0; --i)
        if(f[u][i] != f[v][i]) {
            u = f[u][i];
            v = f[v][i];
        }
    return f[u][0];
}
// 计算树上两点距离
int dist(int u, int v) {
    return d[u] + d[v] - 2*d[lca(u,v)];
}
// 多源BFS预处理最近传送门
void bfs(int n) {
    hd = 1, tl = 0;
    memset(dis, -1, sizeof dis);
    // 初始化传送门节点
    for(int i=1; i<=k; ++i) {
        dis[pt[i]] = 0;
        near[pt[i]] = pt[i];
        q[++tl] = pt[i];
    }
    // BFS
    while(hd <= tl) {
        int u = q[hd++];
        // 遍历邻接节点
        for(int i=g[u]; i; i=nxt[i]) {
            int v = to[i];
            if(dis[v] == -1) {
                dis[v] = dis[u] + 1;
                near[v] = near[u];
                q[++tl] = v;
            }
        }
    }
}
int main() {
    int n, m, q;
    cin >> n >> k >> q;
    // 建树
    for(int i=1; i<n; ++i) {
        int x, y;
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    // 读传送门
    for(int i=1; i<=k; ++i) {
        cin >> pt[i];
        np[pt[i]] = 1;
    }
    // 预处理LCA
    dfs(1, 1);
    // 预处理最近传送门
    if(k) bfs(n);
    // 处理查询
    while(q--) {
        int u, v;
        cin >> u >> v;
        int ans = dist(u, v);  // 直接走的距离
        if(k) ans = min(ans, dis[u] + dis[v]);  // 通过传送门的距离
        cout << ans << endl;
    }
    return 0;
}

学好LCA。

不听我之言,吃亏在眼前。

C. 考试

题面

n n n 位同学,每位同学都参加了全部的 m m m 门课程的期末考试,都在焦急的等待成绩的公布。

i i i 位同学希望在第 t i t_i ti 天或之前得知所有课程的成绩。如果在第 t i t_i ti 天,有至少一门课程的成绩没有公布,他就会等待最后公布成绩的课程公布成绩,每等待一天就会产生 C C C 不愉快度。

对于第 i i i 门课程,按照原本的计划,会在第 b i b_i bi 天公布成绩。

有如下两种操作可以调整公布成绩的时间:

  1. 将负责课程 X X X 的部分老师调整到课程 Y Y Y,调整之后公布课程 X X X 成绩的时间推迟一天,公布课程 Y Y Y 成绩的时间提前一天;每次操作产生 A A A 不愉快度。
  2. 增加一部分老师负责学科 Z Z Z,这将导致学科 Z Z Z 的出成绩时间提前一天;每次操作产生 B B B 不愉快度。

上面两种操作中的参数 X , Y , Z X, Y, Z X,Y,Z 均可任意指定,每种操作均可以执行多次,每次执行时都可以重新指定参数。

现在希望你通过合理的操作,使得最后总的不愉快度之和最小,输出最小的不愉快度之和即可。

思路

一道很好的三分题
但是我竟然写挂了

确定目标时间T的范围:T的可能取值范围是所有学生的 t i t_i ti 和所有课程的 b i b_i bi 的并集,以及可能的中间值。

计算操作成本:对于给定的 T,计算将所有课程调整到不超过 T 的时间所需的最小操作成本。这包括:

  • 使用操作1(调整老师)和操作2(增加老师)来提前课程的公布时间。

计算学生不愉快度:对于给定的 T,计算所有学生的不愉快度,即对于每个学生,如果 t i < T t_i < T ti<T,则产生 C × ( T − t i ) C\times(T - t_i) C×(Tti) 的不愉快度。

寻找最优T:使用三分法在所有可能的T中寻找使总不愉快度最小的那个T,因为总不愉快度关于T的函数通常是单峰的或有一个最小值。

代码

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=1e5+5;
ull a,b,c,n,m,t[N],bi[N],ans=1ull<<60;
ull cal(ull T){
    ull cst=0;
    ull s1=0,s2=0;
    for(int i=1;i<=m;i++)
        if(bi[i]>T) s1+=bi[i]-T;  // 需要提前的天数
        else s2+=T-bi[i];          // 可以推迟的天数
    if(a<b){ 
        ull move=min(s1,s2);
        cst+=a*move; 
        s1-=move; 
    }
    cst+=b*s1;                     // 剩余天数用操作2
    // 计算学生的不愉快度
    for(int i=1;i<=n;i++)
        if(t[i]<T) 
            cst+=(T-t[i])*c;
    return cst;                    // 返回总不愉快度
}
int main(){
    cin>>a>>b>>c>>n>>m;
    for(int i=1;i<=n;i++) cin>>t[i];
    for(int i=1;i<=m;i++) cin>>bi[i];
    // 确定三分搜索的初始范围
    ull l=1;
    ull r=max(*max_element(t+1,t+n+1),
          *max_element(bi+1,bi+m+1));
    // 三分搜索寻找最优解
    while(l<=r){
        ull m1=l+(r-l)/3;
        ull m2=r-(r-l)/3;
        ull c1=cal(m1);     // 计算m1的总不愉快度
        ull c2=cal(m2);     // 计算m2的总不愉快度
        ans=min(ans,min(c1,c2));  // 更新最小值
        if(c1<c2) r=m2-1; 
        else l=m1+1;
    }
    cout<<ans;
    return 0;
}

噢,忘了说了

这里有个小细节,我考试时开了 long long

俗话说不开long long见祖宗

但是还是有一个点 WA:

在这里插入图片描述
调了半天

才发现要开unsigned long long

所以这句话应该这么说:

不开unsigned long long见祖宗

D. 寻宝

本蒟蒻找不到原题,将就看着题面吧

题面

kkkw意外的获得了一张神奇的藏宝图,但想要获得藏宝图中隐藏着的秘密宝藏的地址,就需要解开藏宝图中所隐藏的秘密。

藏宝图上标记着一个 n × n n \times n n×n 大小的表格,表格中填满了各式各样的小写字母,在探索解密的过程中,kkkw可以释放法术,随意修改表格中任
意最多个格子中的字母,将其变为其他任意一个小写字母。当然,kkkw的法术也是有限制的,他最多可以修改 k k k 个格子上的字母。

想要得到藏宝图中的秘密,kkkw需要首先从表格的左上角开始移动,最终移动到表格的右下角为止,每一步可以往右或往下移动一格。这样,从表格的左上角移动到右下角,就一共会经过 2 n − 1 2n-1 2n1 个格子,而这 2 n − 1 2n-1 2n1 个格子中的小写字母将组成一段神奇的文字。而最终秘密宝藏的隐藏地点,就是最多修改个字母后,字典序最小的这一段神奇文字所描述的地方。

现在,请你帮助kkkw找到秘密宝藏被隐藏的地点。

思路

这题有点难度(不止一点

  1. 采用贪心策略结合动态规划的思想:逐位确定最优字符,从第一个位置到第 (2n-1) 个位置依次选择最小可能的字母

  2. 用动态规划跟踪所有可能到达当前位置的坐标及剩余修改次数

  3. 对于每个位置,优先选择最小的字符,同时保留足够的修改次数供后续使用

代码

#include<bits/stdc++.h>
using namespace std;
int main() {
    int n, k;
    cin >> n >> k;
    vector<string> grid(n);
    for (int i = 0; i < n; ++i) {
        cin >> grid[i];
    }
    // 存储当前可能的位置及最大剩余修改次数
    unordered_map<int, int> current;  // key: i * n + j, value: remaining k
    int start_i = 0, start_j = 0;
    int initial_k = k;
    char first_char = grid[start_i][start_j];
    // 处理起始位置
    if (first_char != 'a' && k > 0) {
        initial_k--;
        current[start_i * n + start_j] = initial_k;
    } 
    else {
        current[start_i * n + start_j] = initial_k;
    }
    string result;
    result += (initial_k < k) ? 'a' : first_char;
    // 遍历每一步
    for (int step = 1; step < 2 * n - 1; ++step) {
        unordered_map<int, int> next_pos;
        char min_char = 'z';
        // 第一次遍历:找到当前步骤可能的最小字符
        for (auto &entry : current) {
            int key = entry.first;
            int rk = entry.second;
            int i = key / n;
            int j = key % n;
            // 尝试向右移动
            if (j + 1 < n) {
                int ni = i, nj = j + 1;
                char c = grid[ni][nj];
                char possible_char;
                int new_rk;
                if (rk > 0 && c > 'a') {
                    possible_char = 'a';
                    new_rk = rk - 1;
                } 
                else {
                    possible_char = c;
                    new_rk = rk;
                }
                if (possible_char < min_char) {
                    min_char = possible_char;
                }
            }
            // 尝试向下移动
            if (i + 1 < n) {
                int ni = i + 1, nj = j;
                char c = grid[ni][nj];
                char possible_char;
                int new_rk;
                if (rk > 0 && c > 'a') {
                    possible_char = 'a';
                    new_rk = rk - 1;
                } 
                else {
                    possible_char = c;
                    new_rk = rk;
                }
                if (possible_char < min_char) {
                    min_char = possible_char;
                }
            }
        }
        // 第二次遍历:收集所有能得到最小字符的位置
        for (auto &entry : current) {
            int key = entry.first;
            int rk = entry.second;
            int i = key / n;
            int j = key % n;
            // 尝试向右移动
            if (j + 1 < n) {
                int ni = i, nj = j + 1;
                char c = grid[ni][nj];
                int new_rk;
                if (rk > 0 && c > 'a' && 'a' == min_char) {
                    new_rk = rk - 1;
                    int new_key = ni * n + nj;
                    if (next_pos.find(new_key) == next_pos.end() || new_rk > next_pos[new_key]) {
                        next_pos[new_key] = new_rk;
                    }
                } else if (c == min_char) {
                    new_rk = rk;
                    int new_key = ni * n + nj;
                    if (next_pos.find(new_key) == next_pos.end() || new_rk > next_pos[new_key]) {
                        next_pos[new_key] = new_rk;
                    }
                }
            }
            // 尝试向下移动
            if (i + 1 < n) {
                int ni = i + 1, nj = j;
                char c = grid[ni][nj];
                int new_rk;
                if (rk > 0 && c > 'a' && 'a' == min_char) {
                    new_rk = rk - 1;
                    int new_key = ni * n + nj;
                    if (next_pos.find(new_key) == next_pos.end() || new_rk > next_pos[new_key]) {
                        next_pos[new_key] = new_rk;
                    }
                } 
                else if (c == min_char) {
                    new_rk = rk;
                    int new_key = ni * n + nj;
                    if (next_pos.find(new_key) == next_pos.end() || new_rk > next_pos[new_key]) {
                        next_pos[new_key] = new_rk;
                    }
                }
            }
        }
        result += min_char;
        current = next_pos;
    }
    cout << result;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值