POJ 2886 Who Gets the Most Candies? 树状数组+二分

一、题目大意

我们有N个孩子,每个人带着一张卡片,一起顺时针围成一个圈来玩游戏,第一回合时,第k个孩子被淘汰,然后他说出他卡片上的数字A,如果A是一个正数,那么下一个回合他左边的第A个孩子被淘汰,如果A是一个负数,那么下一个回合,他右边的第(-A)个孩子被淘汰,如下图所示,即A>0,向着下标增大的方向,A<0,向着下标减小的方向。

其中,第 i (1<=i<=N)回合被淘汰的孩子,可以得到F(i)颗糖果,F(i)代表可以整除 i 的因子的数量(包括1和它本身),最终需要输出 N 个孩子中,得到最多的孩子的名字和他得到的糖果数量,假如说有多个孩子都得到这个糖果数量,输出那个最先被淘汰的。

二、解题思路

首先看这个F(i)代表整除 i 的数量,然后针对输入的 N,我们要找出其中使得 F(i)最大的i,同时如果有多个 i 的F(i)一样,取那个最小的 i。

我们可以定义一个数组F[i],对于某一个数字 i,只需要枚举 [1,根号i取整] 范围内的数字 j ,然后如果 i % j == 0,F[i]+=2(因为 j 和 i / j 都是整除 i 的因子,所以需要都加上)然后若 j == i / j ,那么F[i]+=1即可,因为两个乘数相等了。

然后我们根据输入的 N,要迅速找到其中 i ∈[1,N]且 F[i]最大的i,那么考虑可先打表,计算好F[i]数组之后,从1到500000循环,定义一个数组optF,其中optF[i]代表 j∈[1,i]时,使得 f[j]最大的j。给optF[0]=0,然后1<=i<=500000循环,如果F[i]>optF[i-1],则optF[i]=i,否则optF[i]=optF[i-1],这样可以达到两点,1、对于给定的N,直接用optF[N]可以迅速定位到 j ∈ [1,N],使F[j]最大的j。2、当F[p]==F[p+k]时,因为时从1循环,以大于为条件,所以当 p <=N且 p+k<=N时,我们会找到满足条件的顺序最靠前的p。

举个例子吧,

1、N∈[1,1]时,最大的因子数是1,N以内最优的数字也是1;

2、N∈[2,3]时,最大的因子数是2,N以内最优的数字是2;

3、N∈[4,5]时,最大的因子数是3,N以内最优的数字是4;

4、N∈[6,11]时,最大的因子数是4,N以内最优的数字是6;

4、N∈[12,23]时,最大的因子数是6,N以内最优的数字是12;

那么来分析下复杂性,计算F数组的值需要的时间复杂性是 O ( N * 根号N),根据F数组,计算optF数组的复杂性是O(N),对于500000的数据量, O ( N * 根号N)太慢了。那么我们考虑下,hi发现针对 N<=23时,我们只需要记录4个区间的边界和最优的那个F[i]即可。那么对于500000个数字呢?我试了下发现只有35个区间,于是果断写程序把这35个区间的右边界、左边界和最优数字的因子数的打出来的,打表的程序如下。(我发现每次的左边界就是最优数字)

#include <iostream>
using namespace std;
typedef pair<int, int> P;
P tbl[500009];
int f[500009], optF[500009], len;
void initF()
{f[0] = 0;for (int i = 1; i <= 500000; i++){f[i] = 0;for (int j = 1; j * j <= i; j++){if (i % j == 0){f[i] = f[i] + 2;if (j * j == i){f[i] = f[i] - 1;}}}}
}
void initOptF()
{optF[0] = 0;for (int i = 1; i <= 500000; i++){if (f[i] > f[optF[i - 1]]){optF[i] = i;}else{optF[i] = optF[i - 1];}}
}
void printTbl()
{len = 1;tbl[1] = P(1, optF[1]);for (int i = 2; i <= 500000; i++){if (tbl[len].second == optF[i] && i > tbl[len].first){tbl[len].first = i;}else if (tbl[len].second != optF[i]){tbl[++len] = P(i, optF[i]);}}for (int i = 1; i <= len; i++){printf("%d %d %d\n", tbl[i].first, tbl[i].second, f[tbl[i].second]);}printf("%d\n", len);
}
int main()
{initF();initOptF();printTbl();return 0;
}

这样的话,根据任意的N,找出 i ∈[1,N],且F[i]最大的 i(存在F[i]和F[i+k]相同时,自动定到F[i]),同时算出F[i]的值,可以直接根据这张表在O(1)时间内完成。

int n_Tbl[] = {1, 3, 5, 11, 23, 35, 47, 59, 119, 179, 239, 359, 719, 839, 1259, 1679, 2519, 5039, 7559, 10079, 15119, 20159, 25199, 27719, 45359, 50399, 55439, 83159, 110879, 166319, 221759, 277199, 332639, 498959, 500000};
int k_Tbl[] = {1, 2, 4, 6, 12, 24, 36, 48, 60, 120, 180, 240, 360, 720, 840, 1260, 1680, 2520, 5040, 7560, 10080, 15120, 20160, 25200, 27720, 45360, 50400, 55440, 83160, 110880, 166320, 221760, 277200, 332640, 498960};
int f_Tbl[] = {1, 2, 3, 4, 6, 8, 9, 10, 12, 16, 18, 20, 24, 30, 32, 36, 40, 48, 60, 64, 72, 80, 84, 90, 96, 100, 108, 120, 128, 144, 160, 168, 180, 192, 200};

假设,根据N我们找到那个最优数字是optOrder,然后接下来需要考虑的就是,如何找到第 optOrder 个被淘汰的孩子呢?

我的思路是模拟出整个游戏的过程,维护一个树状数组,起初的时候给[1,N]内所有的元素的位置都+1,然后每当第x位置的孩子淘汰的时候,给树状数组update(x,-1)。

使用树状数组+二分查找可以迅速找到当前没有被淘汰的孩子中的第 q 个q∈[1,len],len为当前内有被淘汰的孩子数量,

1、 L = 0 ,R = n+1

2、mid=(L+R)/2

3、query(mid)(树状数组[1,mid]的和)小于q时,L=mid,否则R=mid

4、L+1>=R时,返回 L+1,L+1就是当前未被淘汰的第q个孩子的下标。

那么来考虑这个游戏的过程,一开始的时候第k个孩子被淘汰,然后根据他卡片上的数字 A 判断下一个孩子,卡片上的数字一定不为0。这里我把这个移动分为两步:

1、先在A方向上移动一步到一个当前没有被淘汰的点

2、然后再移动 A - 1步

然后第二步骤移动时,其实是一个关于当前剩余数量的周期运动,如下图所示。

所以我们把可以把移动的过程优化一下,假设第x个孩子被淘汰,第x个孩子的卡片上数字是A,第x个孩子被淘汰以后,剩余孩子的数量是len

1、先在A方向上移动一步到一个当前没有被淘汰的点

2、然后再移动 (A - 1)% len 步

然后这样的话,就变得简单许多了,我们先利用树状前[1,x]项的和找到下标 x 在被淘汰前在孩子们中的顺序y,之后update(x,-1)代表x被淘汰,同时记录剩余孩子的数量为len,

假设是A是朝着数组下标缩小的方向(题目中的右)

记录方向之后,把A变成正数,便于计算。

1、被淘汰的孩子位置在y,先挪一步,到 y-1,判断y-1是否大于 (A-1)%len ,如果是,那么下一个被淘汰孩子就是 y - 1 - ((A-1)%len),如下图所示。

2、如果y-1小于等于 (A-1)%len,那么下一个被淘汰的孩子就是 y - 1 - ((A-1)%len) + len,我给出了2个案例,如下两张图所示。

​​​​​​​

这样就将A向着数组下标缩小方向的所有情况都考虑到了

接下来继续考虑A向着数组下标放大的方向(题目中的左,实际其实是右)

依旧设本次被淘汰的元素在被淘汰前的位置作为y,淘汰掉y之后剩余孩子的数量为len,y手里的数字为A。

1、首先考虑y加上(A-1)%len小于等于len的情况,这种情况下,下一个被淘汰的位置为 y + ((A-1)%len),如下图

2、接下来,再来考虑y加上(A-1)%len大于len的情况,这种情况下,可以画图看出下一个被淘汰的位置为y+((A-1)%len) - len,我给出了两个案例,如下两张图所示。

这样就把所有的4种情况考虑完了,知道当前被淘汰的元素下标k后,可知A,也可以通过树状数组计算出它在队伍中的位置y,然后更新树状数组k的位置为-1,剩余长度len自减1,之后通过位置y、偏移A、队伍长度len和上文中的4个if,求出下个被淘汰的孩子在队伍中的位置,然后根据位置对树状数组进行二分,求出下标,更新k为这个下标,继续下一次的循环,这样循环n次,第 i 循环开始时对应的k,就是第i个被淘汰的孩子,这样就可以知道每个孩子被淘汰的顺序了。

之后输出上文中 第optOrder个被淘汰的孩子的名字,和optOrder对应的F值即可。

总结下吧,像这种情况比较多的问题,最好是画下图,我们不需要背下这4个if,只需要能够记得是4种,然后画个图,一个一个找出来就可以

三、代码

#include <iostream>
using namespace std;
int n_Tbl[] = {1, 3, 5, 11, 23, 35, 47, 59, 119, 179, 239, 359, 719, 839, 1259, 1679, 2519, 5039, 7559, 10079, 15119, 20159, 25199, 27719, 45359, 50399, 55439, 83159, 110879, 166319, 221759, 277199, 332639, 498959, 500000};
int k_Tbl[] = {1, 2, 4, 6, 12, 24, 36, 48, 60, 120, 180, 240, 360, 720, 840, 1260, 1680, 2520, 5040, 7560, 10080, 15120, 20160, 25200, 27720, 45360, 50400, 55440, 83160, 110880, 166320, 221760, 277200, 332640, 498960};
int f_Tbl[] = {1, 2, 3, 4, 6, 8, 9, 10, 12, 16, 18, 20, 24, 30, 32, 36, 40, 48, 60, 64, 72, 80, 84, 90, 96, 100, 108, 120, 128, 144, 160, 168, 180, 192, 200};
int n_, n, bit[524298], card[500009], order[500009], k;
char name[500009][50];
int optFOrder()
{for (int i = 0; i < 35; i++){if (n_ <= n_Tbl[i]){return k_Tbl[i];}}return -1;
}
int f(int num)
{for (int i = 0; i < 35; i++){if (num <= n_Tbl[i]){return f_Tbl[i];}}return -1;
}
void input()
{for (int i = 1; i <= n_; i++){scanf("\n%s %d", &name[i], &card[i]);}
}
void init()
{n = 1;while (n < n_){n = n * 2;}for (int i = 0; i <= n; i++){bit[i] = 0;}
}
void update(int r, int v)
{if (r <= 0){return;}for (int i = r; i <= n; i = i + (i & (-i))){bit[i] = bit[i] + v;}
}
int query(int r)
{int sum = 0;for (int i = r; i > 0; i = i - (i & (-i))){sum = sum + bit[i];}return sum;
}
void push()
{for (int i = 1; i <= n_; i++){update(i, 1);}
}
int binarySearch(int num)
{int l = 0, r = n + 1;while (l + 1 < r){int mid = (l + r) / 2;if (query(mid) < num){l = mid;}else{r = mid;}}return (l + 1);
}
int absVal(int num)
{if (num < 0){return num * (-1);}else{return num;}
}
void solve()
{int len = n_;for (int i = 1; i <= n_; i++){order[i] = k;if (i == n_){break;}len--;int currentOrder = query(k);update(k, -1);bool left = (card[k] < 0);card[k] = absVal(card[k]);// 默认先挪动一步,挪动一步之后就是len下的周期运动card[k]--;card[k] = card[k] % len;if (left && ((currentOrder - 1) > card[k])){k = currentOrder - 1 - card[k];}else if (left && ((currentOrder - 1) <= card[k])){k = currentOrder - 1 - card[k] + len;}else if (!left && (currentOrder + card[k]) <= len){k = currentOrder + card[k];}else if (!left && (currentOrder + card[k]) > len){k = currentOrder + card[k] - len;}k = binarySearch(k);}
}
int main()
{while (~scanf("%d%d", &n_, &k)){input();init();push();solve();printf("%s %d\n", name[order[optFOrder()]], f(n_));}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/95157.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过usb串口发送接收数据

USB通信使用系统api&#xff0c;USB转串口通信使用第三方库usb-serial-for-android&#xff0c; 串口通信使用Google官方库android-serialport-api。x 引入包后在本地下载的位置&#xff1a;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1 在 Android 中&#x…

【python海洋专题十一】colormap调色

【python海洋专题十一】colormap调色 上期内容 本期内容 图像的函数包调用&#xff01; Part01. 自带颜色条Colormap 调用方法&#xff1a; cmap3plt.get_cmap(ocean)查询方法&#xff01; Part02. seaborn函数包 01&#xff1a;sns.cubehelix_palette cmap5 sns.cu…

string类的模拟实现(万字讲解超详细)

目录 前言 1.命名空间的使用 2.string的成员变量 3.构造函数 4.析构函数 5.拷贝构造 5.1 swap交换函数的实现 6.赋值运算符重载 7.迭代器部分 8.数据容量控制 8.1 size和capacity 8.2 empty 9.数据修改部分 9.1 push_back 9.2 append添加字符串 9.3 运算符重载…

OpenCV利用Camshift实现目标追踪

目录 原理 做法 代码实现 结果展示 原理 做法 代码实现 import numpy as np import cv2 as cv# 读取视频 cap cv.VideoCapture(video.mp4)# 检查视频是否成功打开 if not cap.isOpened():print("Error: Cannot open video file.")exit()# 获取第一帧图像&#x…

SpringCloud Alibaba - Sentinel 微服务保护解决雪崩问题、Hystrix 区别、安装及使用

目录 一、Sentinel 1.1、背景&#xff1a;雪崩问题 1.2、雪崩问题的解决办法 1.2.1、超时处理 缺陷&#xff1a;为什么这里只是 “缓解” 雪崩问题&#xff0c;而不是百分之百解决了雪问题呢&#xff1f; 1.2.2、舱壁模式 缺陷&#xff1a;资源浪费 1.2.3、熔断降级 1.…

OK3568 forlinx系统编译过程及问题汇总

1. 共享文件夹无法加载&#xff1b;通过网上把文件夹加载后&#xff0c;拷贝文件很慢&#xff0c;任务管理器查看发现硬盘读写速率很低。解决办法&#xff1a;重新安装vmware tools。 2. 拷贝Linux源码到虚拟机&#xff0c;解压。 3. 虚拟机基本库安装 forlinxubuntu:~$ sudo…

『力扣每日一题12』:只出现一次的数字

一、题目 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff1a; 输入&…

WVP-28181协议视频平台搭建教程

28181协议视频平台搭建教程 安装mysql安装redis安装ZLMediaKit安装28181协议视频平台安装依赖下载源码编译静态页面打包项目, 生成可执行jar修改配置文件启动WVP 项目地址&#xff1a; https://github.com/648540858/wvp-GB28181-pro 说明: wvp-GB28181-pro 依赖redis和mysql中…

案例题--信息系统架构设计

案例题--信息系统架构设计 概念 以扩展了解为主&#xff0c;主要关注图 概念 架构的组成&#xff1a;构件&#xff0c;连接件&#xff0c;约束 构件&#xff1a;组成元素 连接件&#xff1a;构件之间的连接方式 约束&#xff1a;构件和连接件之间的约束 上应&#xff0c;下技&a…

Linux CentOS7 vim多窗口编辑

我们在用vim编辑文件时&#xff0c;有各种需求。如有时需要在多个文件之间来回操作&#xff0c;一会关闭一个文件&#xff0c;一会再打开另外一个文件&#xff0c;这样来回操作显得太笨拙。有时&#xff0c;vim编辑多行的大文件&#xff0c;来回查看、编辑前面一部分及最后一部…

【Axure】元件库和母版、常见的原型规范、静态原型页面制作

添加现有元件库 点击元件库——载入 当然也可以创建元件库&#xff0c;自己画自己保存 建立京东秒杀母版 静态原型页面的制作 框架 选择以iphone8的界面大小为例&#xff0c;顶部状态栏高度为20 左侧类似于标尺&#xff0c;因为图标、文字离最左侧的间距是不一样的 信…

基于Kylin的数据统计分析平台架构设计与实现

目录 1 前言 2 关键模块 2.1 数据仓库的搭建 2.2 ETL 2.3 Kylin数据分析系统 2.4 数据可视化系统 2.5 报表模块 3 最终成果 4 遇到问题 1 前言 这是在TP-LINK公司云平台部门做的一个项目&#xff0c;总体包括云上数据统计平台的架构设计和组件开发&#xff0c;在此只做…

深入了解 Linux 中的 AWK 命令:文本处理的瑞士军刀

简介 在Linux和Unix操作系统中&#xff0c;文本处理是一个常见的任务。AWK命令是一个强大的文本处理工具&#xff0c;专门进行文本截取和分析&#xff0c;它允许你在文本文件中查找、过滤、处理和格式化数据。本文将深入介绍Linux中的AWK命令&#xff0c;让你了解其基本用法和…

ElasticSearch第四讲:ES详解:ElasticSearch和Kibana安装

ElasticSearch第四讲&#xff1a;ES详解&#xff1a;ElasticSearch和Kibana安装 本文是ElasticSearch第四讲&#xff1a;ElasticSearch和Kibana安装&#xff0c;主要介绍ElasticSearch和Kibana的安装。了解完ElasticSearch基础和Elastic Stack生态后&#xff0c;我们便可以开始…

数据库的备份与恢复

数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中&#xff0c;数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 造成数据丢失的原因&#xff1a; 程序错误人为操作错误运算错误磁盘故障灾难&#xff08;如火灾、地震&#xff09;和盗窃 数据库备份…

XC5013 马达驱动和充电集成一体的控制芯片 一档输出芯片

XC5013 是一款应用于马达驱动或 LED 驱动的控制芯片&#xff0c;集成了锂电池充电管理系统&#xff0c;设定一档高电平输 出&#xff0c;并带有对不同状态的 LED 指示功能。 XC5013 集成了涓流充电、恒流充电和恒压充电全过程的充电方式&#xff0c;浮充电压精度在全温度范…

了解基于Elasticsearch 的站内搜索,及其替代方案

对于一家公司而言&#xff0c;数据量越来越多&#xff0c;如果快速去查找这些信息是一个很难的问题&#xff0c;在计算机领域有一个专门的领域IR&#xff08;Information Retrival&#xff09;研究如何获取信息&#xff0c;做信息检索。在国内的如百度这样的搜索引擎也属于这个…

二叉树经典例题

前言&#xff1a; 本文主要讲解了关于二叉树的简单经典的例题。 因为二叉树的特性&#xff0c;所以关于二叉树的大部分题目&#xff0c;需要利用分治的思想去递归解决问题。 分治思想&#xff1a; 把大问题化简成小问题&#xff08;根节点、左子树、右子树&#xff09;&…

leetCode 53.最大子数和 图解 + 贪心算法/动态规划+优化

53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 1&#xff1a; 输入…

VUE3照本宣科——路由与状态管理器

VUE3照本宣科——路由与状态管理器 前言一、路由&#xff08;router&#xff09;1.createRouter2.router-link3.router-view4.useRoute5.useRouter6.路由守卫7.嵌套路由 二、状态管理器&#xff08;Pinia&#xff09;1.定义Store&#xff08;1&#xff09;Option Store&#x…