官网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 自动去重存储每个集合,查询时统计交集元素数量,再通过「并集大小 = 两集合大小之和 - 交集大小」计算分母,最终得到百分比结果。
算法步骤:
- 读取 N 个集合,每个集合的元素存入
set自动去重,按编号保存。 - 读取 K 次查询,每次取出两个集合。
- 遍历其中一个集合的所有元素,若该元素在另一个集合中存在,则交集计数加 1。
- 代入公式计算相似度,按格式输出两位小数和百分号。
正解代码
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实现层序遍历。
算法步骤:
- 分别存储后序遍历数组
suf和中序遍历数组in。 - 递归函数
build(il, ir, sl, sr):il、ir是中序区间,sl、sr是后序区间。 - 取后序末尾元素
suf[sr]作为根节点值,在中序数组中找到根的下标pos。 - 左子树长度
len = pos - il,左子树中序区间为[il, pos-1],后序区间为[sl, sl+len-1]。 - 右子树中序区间为
[pos+1, ir],后序区间为[sl+len, sr-1]。 - 递归构建左右子树并返回根节点指针。
- 使用队列进行 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。
- 读取每条人员信息,先暂存所有亲属关系,同时标记所有出现过的有效编号。
- 遍历所有有效编号,将本人与父母、子女分别合并,合并时始终让编号更小的作为根节点。
- 合并过程中将房产套数、面积、人口数累加到根节点上。
- 遍历 0~9999 所有编号,找出满足「存在且是根节点」的家庭,存入结果数组。
- 按人均面积降序、最小编号升序的规则排序。
- 按格式输出,编号不足 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 最长对称子串
题目大意:给定一个字符串,求其中最长的对称(回文)子串的长度。
核心思路:采用中心扩展法,枚举每一个可能的回文中心,向左右两端扩展直到字符不相等,记录过程中的最大回文长度。回文分为奇数长度(中心为单个字符)和偶数长度(中心为两个字符之间)两种,需分别处理。
算法步骤:
- 遍历字符串每个位置i:
- 奇数长度回文:以i为中心,左右指针向两侧扩展,统计回文长度。
- 偶数长度回文:以i与i+1之间为中心,左右指针向两侧扩展,统计回文长度。
- 每次扩展结束后,更新全局最大回文长度。
- 遍历完成后输出最大长度。
正解代码
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²)的中心扩展法可稳定通过。