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

news2024/11/20 8:24:49

一、题目大意

我们有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.coloradmin.cn/o/1062215.html

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

相关文章

JAVA面经整理(7)

一)什么是AQS&#xff1f; 1)AQS也被称之为是抽象同步队列&#xff0c;它是JUC包底下的多个组件的底层实现&#xff0c;Lock&#xff0c;CountDownLatch和Semphore底层都使用到了AQS AQS的核心思想就是给予一个等待队列和同步状态来实现的&#xff0c;它的内部使用一个先进先出…

Ubuntu使用cmake和vscode开发自己的项目,引用自己的头文件和openCV

创建文件夹 mkdir my_proj 继续创建include 和 src文件夹&#xff0c;形成如下的目录结构 用vscode打开项目 创建add.h #ifndef ADD_H #define ADD_Hint add(int numA, int numB);#endif add.cpp #include "add.h"int add(int numA, int numB) {return numA nu…

【springboot+vue】原生小程序电子班牌系统 智慧校园云平台源码

智慧校园云平台电子班牌系统源码 智慧班牌全套源码 智慧校园云平台电子班牌系统&#xff0c;集学生管理、班级管理、校园管理于一身&#xff0c;融合学校教务管理、教师管理、学籍管理、考勤、信息发布、班级文明建设、校园风采、家校互通等一系列应用&#xff0c;为校园管理现…

javaee spring整合mybatis

案例一 包含dao层 创建maven webapp项目 maven仓库需要改为阿里云 引入依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-inst…

【赠书活动第3期】《构建新型网络形态下的网络空间安全体系》——用“价值”的视角来看安全

目录 一、内容简介二、读者受众三、图书目录四、编辑推荐五、获奖名单 一、内容简介 经过30多年的发展&#xff0c;安全已经深入到信息化的方方面面&#xff0c;形成了一个庞大的产业和复杂的理论、技术和产品体系。 因此&#xff0c;需要站在网络空间的高度看待安全与网络的…

面向无线传感器网络WSN的增强型MODLEACH设计与仿真(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Spring Cloud Gateway实现数字签名与URL动态加密

文章目录 什么是数字签名&#xff1f;Spring Cloud Gateway的基础实现数字签名与URL动态加密步骤1&#xff1a;添加依赖步骤2&#xff1a;配置路由步骤3&#xff1a;实现数字签名过滤器步骤4&#xff1a;实现数字签名验证步骤5&#xff1a;实现URL动态加密 结论 &#x1f389;欢…

FFmpeg 命令:从入门到精通 | FFmpeg 解码流程

FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 解码流程 FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 解码流程流程图FFmpeg 解码的函数FFmpeg 解码的数据结构补充小知识 FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 解码流程 本内容参考雷霄骅博士的 FFmpeg 教程。 流…

视频降噪一些原理

视频降噪&#xff0c;除去部分有可能错误的信息&#xff0c;替换为猜测的可能正确的信息。 真实的细节减少。 亮度低是因为光子少。光子多亮度高。 误差存在&#xff0c;放大倍数越大&#xff0c;误差越大&#xff0c;就会显得噪点越多。 减少噪点&#xff1a; 增加进光量&a…

动图gif怎么做?分享一招超简单方法

常见的图片格式有jpg、png以及gif格式&#xff0c;其中gif格式的图片因为其画面内容丰富生动所以深受大家的喜爱。那么&#xff0c;如何将jpg、png格式的图片转换成gif格式动图呢&#xff1f;通过使用GIF中文网的gif制作&#xff08;https://www.gif.cn/&#xff09;功能&#…

[管理与领导-113]:IT人看清职场中的隐性规则 - 10 - 看清人的行动、行为、手段、方法背后的动机与背景条件

目录 前言&#xff1a; 一、冰山模型 1.1 冰山模型&#xff0c;系统思考的工具 1.2 冰山模型&#xff1a;发现人行为背后的动机 二、动机、行为模型 "说一套"&#xff1a; "做一套"&#xff1a; "演一套"&#xff1a; "学一套&quo…

C++笔记之不同buffer数量下的生产者-消费者机制

C笔记之不同buffer数量下的生产者-消费者机制 文章目录 C笔记之不同buffer数量下的生产者-消费者机制0.在不同的缓冲区数量下&#xff0c;生产者-消费者机制的实现方式和行为的区别1.最简单的生产者-消费者实现&#xff1a;抄自 https://mp.weixin.qq.com/s/G1lHNcbYU1lUlfugXn…

手机或者电脑连接局域网内的虚拟机(网桥)

手机或者电脑连接局域网内的虚拟机&#xff08;网桥&#xff09; 手机软件&#xff1a;ConnectBot&#xff0c;Termius&#xff0c;JuiceSSH … 1.虚拟机vmware中添加桥接网卡 这里桥接网卡选择的是自动&#xff0c;是自动生成动态IP&#xff0c;如果不需要动态生成&#xff…

通达信和同花顺能否实现程序化自动交易股票,量化交易如何实现?

以下写给正在寻找自动交易接口的朋友&#xff0c;首先&#xff0c;不是那种设置个简单条件的条件单&#xff0c;或者某些客户端上形同鸡肋的策略交易&#xff0c;那些策略根本称不上策略&#xff0c;还有各种限制&#xff0c;不支持这个不支持那个&#xff0c;可设置的参数也不…

通过融合UGV的地图信息和IMU的惯性测量数据,实现对车辆精确位置和运动状态的估计和跟踪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【MySQL】表的基础增删改查

前面我们已经知道怎么来创建表了&#xff0c;接下来就来对创建的表进行一些基本操作。 这里先将上次创建的表删除掉&#xff1a; mysql> use test; Database changedmysql> show tables; ---------------- | Tables_in_test | ---------------- | student | -----…

redis持久化与调优

一 、Redis 高可用&#xff1a; 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#x…

联想M7216NWA一体机连接WiFi及手机添加打印机方法

联想M7216NWA一体机连接WiFi方法&#xff1a; 1、首先按打印机操作面板上的“功能键”&#xff1b;【用“”&#xff08;上翻页&#xff09;“-”&#xff08;下翻页&#xff09;来选择菜单的内容】 2、下翻页键找到并选择“网络”&#xff0c;然后“确认键”&#xff1b; 3…

javaee ssm框架整合例子 ssm例子,需要哪些依赖,配置文件如何配置

项目结构 步骤一&#xff0c;创建springmybatis项目 参考上一篇博客 步骤二&#xff0c;融入SpringMVC 添加依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http:…

ArcGIS Engine:视图菜单的创建和鹰眼图的实现

目录 01 创建项目 1.1 通过ArcGIS-ExtendingArcObjects创建窗体应用 1.2 通过C#-Windows窗体应用创建窗体应用 1.2.1 创建基础项目 1.2.2 搭建界面 02 创建视图菜单 03 鹰眼图的实现 3.1 OnMapReplaced事件的触发 3.2 OnExtentUpdated事件的触发 04 稍作演示 01 创建项目…