PTA团体程序设计天梯赛L2真题讲解L2-005-008

官网https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7

文章目录

      • [L2-005 集合相似度](#L2-005 集合相似度)
      • [L2-006 树的遍历](#L2-006 树的遍历)
      • [L2-007 家庭房产](#L2-007 家庭房产)
      • [L2-008 最长对称子串](#L2-008 最长对称子串)

L2-005 集合相似度

题目大意:给定 N 个整数集合,每次查询两个集合,计算它们的相似度。相似度公式为:两个集合交集的不同元素个数 ÷ 并集的不同元素个数 × 100%,结果保留两位小数。

核心思路 :使用 set 自动去重存储每个集合,查询时统计交集元素数量,再通过「并集大小 = 两集合大小之和 - 交集大小」计算分母,最终得到百分比结果。

算法步骤

  1. 读取 N 个集合,每个集合的元素存入 set 自动去重,按编号保存。
  2. 读取 K 次查询,每次取出两个集合。
  3. 遍历其中一个集合的所有元素,若该元素在另一个集合中存在,则交集计数加 1。
  4. 代入公式计算相似度,按格式输出两位小数和百分号。

正解代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int n,m,t,k,x,y;
map<int,set<int>>mp;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>m;
        set<int>se;
        for(int j=0;j<m;j++){
            cin>>x;
            se.insert(x);
        }
        mp[i]=se;
    }
    cin>>k;
    for(int i=0;i<k;i++){
        int x,y;
        cin>>x>>y;
        int nc=0,nt=0;
        nt=mp[x].size()+mp[y].size();
        for(auto nt:mp[x])if(mp[y].count(nt))nc++;
        nt-=nc;
        double ans=nc*100.0/nt;
        printf("%.2f\%\n",ans);
        
    }

    return 0;
}

代码关键细节

  • vector<set<int>>map<int, set<int>> 存储集合,编号从 1 开始对应输入。
  • 遍历较小的集合可以减少循环次数,本题数据范围不大,直接遍历任一集合均可通过。
  • 输出使用 printf("%.2f%%\n", ans),注意百分号需要转义。

L2-006 树的遍历

题目大意:给定一棵二叉树的后序遍历和中序遍历序列,输出该树的层序遍历序列,节点值均为互不相同的正整数。

核心思路 :递归构建二叉树。后序遍历的最后一个元素是根节点,在中序序列中定位根节点位置,即可划分出左右子树的中序区间,再对应算出左右子树的后序区间,递归完成建树。最后用BFS实现层序遍历。

算法步骤

  1. 分别存储后序遍历数组 suf 和中序遍历数组 in
  2. 递归函数 build(il, ir, sl, sr)il、ir 是中序区间,sl、sr 是后序区间。
  3. 取后序末尾元素 suf[sr] 作为根节点值,在中序数组中找到根的下标 pos
  4. 左子树长度 len = pos - il,左子树中序区间为 [il, pos-1],后序区间为 [sl, sl+len-1]
  5. 右子树中序区间为 [pos+1, ir],后序区间为 [sl+len, sr-1]
  6. 递归构建左右子树并返回根节点指针。
  7. 使用队列进行 BFS 层序遍历,依次出队输出节点值。

正解代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int n,m,t,k,x,y;
int suf[49],in[49];
struct nd{
    int val;
    nd* lef=NULL;
    nd* rig=NULL;
};
nd* build(int il,int ir,int sl,int sr){
    if(il>ir)return NULL;
    int root=suf[sr];
    nd* p=new nd;
    p->val=root;
    if(il==ir)return p;
    int pos=il;
    while(in[pos]!=root)pos++;
    int len=pos-1-il+1;
    p->lef=build(il,pos-1,sl,sl+len-1);
    p->rig=build(pos+1,ir,sl+len,sr-1);
    return p;
}
queue<nd*>q;
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>suf[i];
    for(int i=0;i<n;i++)cin>>in[i];
    int root=suf[n-1];
    nd* head=new nd;
    head=build(0,n-1,0,n-1);
    q.push(head);
    while(q.size()){
        auto nt=q.front();q.pop();
        if(nt->val!=root)cout<<" ";
        cout<<nt->val;
        if(NULL!=nt->lef)q.push(nt->lef);
        if(NULL!=nt->rig)q.push(nt->rig);
    }


    return 0;
}

代码关键细节

  • 节点结构体包含节点值和左右孩子指针,初始指针置为 NULL
  • 递归边界:il > ir 时返回空指针;il == ir 时直接返回叶子节点。
  • 层序输出时空格处理:第一个元素前不输出空格,后续元素前输出空格,避免行尾多余空格。

L2-007 家庭房产

题目大意:给定每个人的父母、子女信息以及名下房产套数和总面积,按家庭统计人口数、人均房产套数和人均面积。结果按人均面积降序输出,并列则按家庭最小编号升序。

核心思路 :使用并查集维护家庭亲属关系,合并集合时同步维护每个家庭的总人口、总房产套数和总面积。统一以小编号作为家庭代表,最后收集所有家庭根节点并排序输出。

算法步骤

  1. 初始化并查集,所有编号的父节点初始为自身,人口数初始为 1。
  2. 读取每条人员信息,先暂存所有亲属关系,同时标记所有出现过的有效编号。
  3. 遍历所有有效编号,将本人与父母、子女分别合并,合并时始终让编号更小的作为根节点。
  4. 合并过程中将房产套数、面积、人口数累加到根节点上。
  5. 遍历 0~9999 所有编号,找出满足「存在且是根节点」的家庭,存入结果数组。
  6. 按人均面积降序、最小编号升序的规则排序。
  7. 按格式输出,编号不足 4 位时前面补零。

正解代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int f[N],hs[N],n,s[N],p[N];//父节点 房子数 面积数 人数
struct fmy{
    int id,cntp,cnts,cnths;
    double perhs,pers;
    bool operator <(fmy fam)const {
        if(pers!=fam.pers)return pers>fam.pers;
        return id<fam.id;
    }
};
vector<fmy>v;
vector<int>ff[N];//先读入完再合并
int find(int x){
    if(f[x]!=x)f[x]=find(f[x]);
    return f[x];
}
void hebing(int a,int b){
    int aa=find(a),bb=find(b);
    if(aa>bb)swap(aa,bb);
    if(aa==bb)return ;
    f[bb]=aa;//小的为家庭代表
    // 这里不合并财产,等所有关系建立后再合并
     hs[aa]+=hs[bb];
    s[aa]+=s[bb];
    p[aa]+=p[bb];
}

int main(){
    cin>>n;
    for(int i=0;i<N;i++)f[i]=i;//初始化
    
    
    int id,dad,mom,cnt;
    
    for(int i=0;i<n;i++){
        cin>>id>>dad>>mom>>cnt;
        int kid;
        
        // 标记存在的节点并初始化人数
        p[id]=1;
        if(dad!=-1){
            ff[id].push_back(dad);
            p[dad]=1;
        }
        if(mom!=-1){
            ff[id].push_back(mom);
            p[mom]=1;
        }
        for(int j=0;j<cnt;j++){
            cin>>kid;
            ff[id].push_back(kid);
            p[kid]=1;
        }
        cin>>hs[id]>>s[id];
    }
    
    // 先建立所有关系
    for(int i=0;i<10000;i++)
        for(int j=0;j<ff[i].size();j++)
            hebing(i,ff[i][j]);
    
    
    for(int i=0;i<10000;i++)
        if(p[i]>0&&i==find(i)){ // 存在且是根节点
            v.push_back({i,p[i],s[i],hs[i],
                1.0*hs[i]/p[i],1.0*s[i]/p[i]});
        }
    sort(v.begin(),v.end());
    cout<<v.size()<<'\n';
     for(int i=0;i<v.size();i++)
printf("%04d %d %.3f %.3f\n",
       v[i].id,v[i].cntp,v[i].perhs,v[i].pers);
         
    return 0;
}

代码关键细节

  • 用二维数组暂存亲属关系,读完所有数据后再统一合并,避免边读边合并不完整。
  • 并查集加入路径压缩优化,提升查找效率。
  • 输出使用 %04d 格式化编号,自动补前导零到 4 位。

L2-008 最长对称子串

题目大意:给定一个字符串,求其中最长的对称(回文)子串的长度。

核心思路:采用中心扩展法,枚举每一个可能的回文中心,向左右两端扩展直到字符不相等,记录过程中的最大回文长度。回文分为奇数长度(中心为单个字符)和偶数长度(中心为两个字符之间)两种,需分别处理。

算法步骤

  1. 遍历字符串每个位置i:
    • 奇数长度回文:以i为中心,左右指针向两侧扩展,统计回文长度。
    • 偶数长度回文:以i与i+1之间为中心,左右指针向两侧扩展,统计回文长度。
  2. 每次扩展结束后,更新全局最大回文长度。
  3. 遍历完成后输出最大长度。

正解代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
    string s;
    getline(cin,s);
    int n=s.size();
    int ans=0;
    for(int i=0;i<n;i++){
        int l=i-1,r=i+1;
        while(l>=0&&r<n&&s[l]==s[r]){
            l--;r++;
        }
        l++;r--;
        ans=max(ans,r-l+1);
        l=i,r=i+1;
        while(l>=0&&r<n&&s[l]==s[r]){
            l--;r++;
        }
        l++;r--;
        ans=max(ans,r-l+1);
    }
    cout<<ans;


    return 0;
}

代码关键细节

  • 扩展循环结束后指针会多移动一步,需要回退一位再计算长度。
  • 输入可能包含空格、特殊符号,必须使用getline读取整行字符串。
  • 字符串长度不超过1000,O(n²)的中心扩展法可稳定通过。
相关推荐
看浪的路人1 小时前
第2讲:一致性哈希——数据分片与负载均衡
算法·哈希算法
君顾11 小时前
代驾跑腿业务系统实战开发指南
java·开发语言·代驾跑腿
imaol11 小时前
数据结构--树
数据结构
夏幻灵2 小时前
从语法坑点到 JS 内存底层:Vue 3 watch 侦听器深度全解
开发语言·javascript·vue.js
阿维的博客日记2 小时前
保姆级教程介绍分词算法BPE
人工智能·算法·分词算法·bpe
Tongzhi20262 小时前
通芝科技无感考勤一体机:软硬一体,开箱即用
数据结构·数据库·科技·算法·均值算法·数据库开发
k4m7v2pz2 小时前
Rust 长跑守护进程日志治理:切分、时区与结构化
开发语言·后端·rust·日志系统·日志轮转·ndjson
FfHUCisI2 小时前
Golang RESTful API 设计原则
开发语言·golang·restful
breeze jiang2 小时前
ESLint flat config 配置实战:五大字段、规则严重级别与 --fix 能力边界详解
开发语言·前端·javascript