news 2026/4/23 11:26:49

打击犯罪(black)(信息学奥赛一本通- P1386)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
打击犯罪(black)(信息学奥赛一本通- P1386)

【题目描述】

某个地区有n(n<=1000)个犯罪团伙,当地警方按照他们的危险程度由高到低给他们编号为1-n,他们有些团伙之间有直接联系,但是任意两个团伙都可以通过直接或间接的方式联系,这样这里就形成了一个庞大的犯罪集团,犯罪集团的危险程度由集团内的犯罪团伙数量唯一确定,而与单个犯罪团伙的危险程度无关(该犯罪集团的危险程度为n)。现在当地警方希望花尽量少的时间(即打击掉尽量少的团伙),使得庞大的犯罪集团分离成若干个较小的集团,并且他们中最大的一个的危险程度不超过n/2。为达到最好的效果,他们将按顺序打击掉编号1到k的犯罪团伙,请编程求出k的最小值。

【输入】

第一行一个正整数n。接下来的n行每行有若干个正整数,第一个整数表示该行除第一个外还有多少个整数,若第i行存在正整数k,表示i,k两个团伙可以直接联系。

【输出】

一个正整数,为k的最小值。

【输入样例】

7 2 2 5 3 1 3 4 2 2 4 2 2 3 3 1 6 7 2 5 7 2 5 6

【输出样例】

1

【提示】

【提示】

输出1(打击掉犯罪团伙)

1. 题目分析

问题核心:

我们需要按顺序(从1到k)打击掉前k个犯罪团伙成员。目标是求出最小的k,使得剩下的所有人中,最大的连通块(犯罪团伙)的大小不超过n/2。

难点:

并查集是一个非常擅长“合并”的数据结构,但它不擅长“删除”。如果按照题目正向思维,每删除一个人就要重新构建并查集,时间复杂度会爆炸。

2. 核心思路:

既然“删除”很难,那我们就把过程反过来

  1. 假设:我们先把1到n所有人全部“删除”(视为不存在)。

  2. 复原:我们从第n个人开始,倒着往回一个一个“复活”(加入集合)。

    • 第 1 步:复活n。

    • 第 2 步:复活n-1。

    • ...

    • 第i步:复活i。

  3. 判定:

    当我们“复活”了第i个人,并把他和已经存在的(编号大于i的)朋友合并后,如果发现此时最大的犯罪团伙人数超过了n/2。

    • 这就意味着:只要i存在,团伙就太大了。

    • 结论:为了不让团伙过大,我们必须把i也打击掉。所以i就是那个临界点,答案k=i。

3. 关键逻辑细节

  • 只连“大于i”的人:在倒序遍历到i时,我们只能把i和编号大于i的朋友合并。因为编号小于i的人此时还处于“未复活”状态。

  • 统计大小:每次合并后,我们需要扫描一遍当前所有“复活”的人,用一个d数组统计每个根节点下有多少人,从而找出最大值。

4. 完整代码

//为达到最好的效果,他们将按顺序打击掉 //编号1到k的犯罪团伙,请编程求出k的最小值 //k从小到大删,每删掉一个,代表输入的那一行的关系都不复存在 //所以我们可以倒着求,假设都删掉了,从最后可以开始复原,加入集合,求并查集 //当加到第i行时,犯罪团伙集团中最大的一个危险程度刚好大于n/2了 //就代表k的最小值就是i #include <iostream> #include <vector> using namespace std; vector<int> vec[1005];//存储输入的每一行的信息 int fa[1001];//记录每个犯罪团伙的集团老大 int d[1010];//记录每个犯罪集团的危险程度(每个老大集团有多少人) //查询 并进行路径压缩 int find(int x){ if(fa[x]==x) return x;//如果x的犯罪集团的老大就是x,就返回 //否则就递归求集团老大,并把路径上每个犯罪集团的老大变为最终老大 return fa[x]=find(fa[x]); } void uni(int x,int y){ int fax=find(x);//找到x的集团老大 int fay=find(y);//找到y的集团老大 //如果老大相同,就不需要任何操作 //否则就把x的集团老大变成y的集团老大的老大 if(fax!=fay){ fa[fay]=fax; } } int main(){ //关闭同步,加速 ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; //初始化每个犯罪团伙的老大是自己 for(int i=1;i<=n;i++) fa[i]=i; //把每一行输入信息都存进vector for(int i=1;i<=n;i++){ int m;//代表第i行有多少个数 cin>>m; for(int j=1;j<=m;j++){ int x; cin>>x; vec[i].push_back(x); } } //倒着遍历vector每一行 从第n行开始 //直到最大犯罪集团危险程度刚好超过n/2,就输出此时的i for(int i=n;i>=1;i--){ //把每一行的每一个大于i的元素和i放入同一个集团 //因为他们有直接联系 小于i的元素还没有复原 所以不能合并 for(int j=0;j<vec[i].size();j++){ //如果这一行的元素值大于i 就可以和i属于一个犯罪集团 if(vec[i][j]>i){ uni(i,vec[i][j]); } } int ma=1;//记录最大犯罪集团的危险程度 //接下来判断每个集团的危险程度 //d数组记录每个犯罪集团的危险程度(每个老大集团有多少人) memset(d,0,sizeof(d));//每轮要初始化d数组为0 //先做一次路径压缩,确保统计准确(也可以直接在find里做) for(int j=i;j<=n;j++) find(j); //因为现在比i小的都已经删掉了,还没复原,所以从i开始统计即可 for(int j=i;j<=n;j++){ d[find(j)]++;//找到j的老大,给这个老大的犯罪集团人数+1 ma=max(ma,d[find(j)]); } //当危险程度大于n/2时 说吗i必须删除 就输出当前i结束程序 if(ma>n/2){ cout<<i; return 0; } } return 0; }

5. 优化思考

目前的解法在统计人数时,每次都要遍历 i->n,这使得内部复杂度是O(N),总复杂度接近 O(N^2)。对于N=1000的数据范围是完全没问题的。

如果N很大怎么办?

我们可以在并查集中维护一个 size[] 数组。

  1. 初始化size[i]=1

  2. uni(x,y)合并时,直接size[fax]+=size[fay],并实时维护一个全局最大值current_max

  3. 这样就不需要里面的memset和循环统计了,可以将复杂度降到接近O(N)。

优化后完整代码

//优化到O(n) //为达到最好的效果,他们将按顺序打击掉 //编号1到k的犯罪团伙,请编程求出k的最小值 //k从小到大删,每删掉一个,代表输入的那一行的关系都不复存在 //所以我们可以倒着求,假设都删掉了,从最后可以开始复原,加入集合,求并查集 //当加到第i行时,犯罪团伙集团中最大的一个危险程度刚好大于n/2了 //就代表k的最小值就是i #include <iostream> #include <vector> using namespace std; vector<int> vec[1005];//存储输入的每一行的信息 int fa[1001];//记录每个犯罪团伙的集团老大 int d[1010];//记录每个犯罪集团的危险程度(每个老大集团有多少人) int sz[1010];//记录每个犯罪集团有多少人 int ma;//记录最大犯罪集团的危险程度 //查询 并进行路径压缩 int find(int x){ if(fa[x]==x) return x;//如果x的犯罪集团的老大就是x,就返回 //否则就递归求集团老大,并把路径上每个犯罪集团的老大变为最终老大 return fa[x]=find(fa[x]); } void uni(int x,int y){ int fax=find(x);//找到x的集团老大 int fay=find(y);//找到y的集团老大 //如果老大相同,就不需要任何操作 //否则就把x的集团老大变成y的集团老大的老大 if(fax!=fay){ fa[fay]=fax; sz[fax]+=sz[fay];//fay的老大变成了fax,所以fay集团的人数要加到fax上 ma=max(ma,sz[fax]); } } int main(){ //关闭同步,加速 ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; for(int i=1;i<=n;i++){ fa[i]=i;//初始化每个犯罪团伙的老大是自己 sz[i]=1;//初始化每个犯罪团队都是一个犯罪集团,所以每个犯罪集团一个人 } //把每一行输入信息都存进vector for(int i=1;i<=n;i++){ int m;//代表第i行有多少个数 cin>>m; for(int j=1;j<=m;j++){ int x; cin>>x; vec[i].push_back(x); } } //倒着遍历vector每一行 从第n行开始 //直到最大犯罪集团危险程度刚好超过n/2,就输出此时的i for(int i=n;i>=1;i--){ //把每一行的每一个大于i的元素和i放入同一个集团 //因为他们有直接联系 小于i的元素还没有复原 所以不能合并 for(int j=0;j<vec[i].size();j++){ //如果这一行的元素值大于i 就可以和i属于一个犯罪集团 if(vec[i][j]>i){ uni(i,vec[i][j]); } } //当危险程度大于n/2时 说吗i必须删除 就输出当前i结束程序 if(ma>n/2){ cout<<i; return 0; } } return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/8 10:59:22

PinWin:Windows窗口置顶的终极解决方案,彻底告别频繁切换窗口

PinWin&#xff1a;Windows窗口置顶的终极解决方案&#xff0c;彻底告别频繁切换窗口 【免费下载链接】PinWin Pin any window to be always on top of the screen 项目地址: https://gitcode.com/gh_mirrors/pin/PinWin 在日常电脑使用中&#xff0c;你是否经常需要在多…

作者头像 李华
网站建设 2026/4/20 4:14:00

2026年最新Web安全入门学习,全面掌握Web安全,看这一篇就够了

“未知攻&#xff0c;焉知防”——真正的安全始于理解攻击者的思维 在日益数字化的世界中&#xff0c;Web安全工程师已成为企业防护体系的“数字盾牌”。本文将提供一条清晰的进阶路径&#xff0c;助你在2025年的网络安全领域脱颖而出。 一、认知篇&#xff1a;理解安全本质 …

作者头像 李华
网站建设 2026/4/23 11:12:05

彻底解决老旧Mac显示问题:投影仪与多屏输出完美方案

彻底解决老旧Mac显示问题&#xff1a;投影仪与多屏输出完美方案 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher&#xff08;OCLP&#xff09;是专…

作者头像 李华
网站建设 2026/4/23 11:15:14

洛雪音乐音源修复终极指南:快速解决音乐播放失效问题

洛雪音乐音源修复终极指南&#xff1a;快速解决音乐播放失效问题 【免费下载链接】New_lxmusic_source 六音音源修复版 项目地址: https://gitcode.com/gh_mirrors/ne/New_lxmusic_source 当您发现洛雪音乐客户端无法正常播放音乐时&#xff0c;很可能是音源模块出现了问…

作者头像 李华
网站建设 2026/4/23 11:11:56

TaskbarX任务栏居中工具:让你的Windows桌面焕然一新

TaskbarX任务栏居中工具&#xff1a;让你的Windows桌面焕然一新 【免费下载链接】TaskbarX Center Windows taskbar icons with a variety of animations and options. 项目地址: https://gitcode.com/gh_mirrors/ta/TaskbarX 还在为Windows任务栏图标杂乱无章而烦恼吗&…

作者头像 李华
网站建设 2026/4/22 21:11:25

手部姿态识别实战:彩虹骨骼可视化技术揭秘

手部姿态识别实战&#xff1a;彩虹骨骼可视化技术揭秘 1. 引言&#xff1a;AI手势识别的现实意义与应用前景 随着人机交互技术的不断演进&#xff0c;手势识别正逐步从科幻场景走向日常生活。无论是智能驾驶中的非接触控制、AR/VR中的自然交互&#xff0c;还是智能家居的远程…

作者头像 李华