EOJ 2020 1月月赛 E数的变换

2 篇文章 0 订阅

传送门

Cuber QQ 正在刷 EOJ 上的水题,他正在做的一道题目是这样的。

给定一个正整数 x :

如果 x 是奇数的话,则变幻成 x−1 ;
如果 x 是偶数的话,则变幻成 x * 2 。
如此往复地执行这个操作,直到 x 变为 1 。

显然这对于 Cuber QQ 来说过于简单了。于是 Cuber QQ 根据这个发明了一个序列,称为变幻序列, x -变幻序列指的是,从 x 作为变幻的开始,一直变幻到 1 所构成的序列,例如 7 -变幻序列是 {7,6,3,2,1} ; 10 -变幻序列是 {10,5,4,2,1} 。

而现在 Cuber QQ 在纸上写出了所有 1 到 n 变幻序列,他分别统计了每一个数在这些序列中出现的次数,例如当 n=4 的时候,四个序列分别是 [1]={1},[2]={2,1},[3]={3,2,1},[4]={4,2,1} ,则 数 1 出现了 4 次,数 2 出现了 3 次 ,数 3 出现了 1 次 ,数 4 出现了 1 次。

现在 Cuber QQ 想知道最大的数 x 满足 x 在所有 1 到 n 变幻序列中至少出现了 k 次。

输入格式
第一行包含一个整数 T(1≤T≤104) ,表示数据组数。

对于每一组数据包含两个整数 n,k(1≤k≤n≤1018) ,含义如题面所述。

输出格式
对于每一组数据,输出一行一个整数表示答案。

样例
input
4
4 1
4 2
4 3
4 4
output
4
2
2
1

题目中 x -变幻序列的生成方式,在二进制的意义下,无非就是:

  • 末尾为 1 ,则变成 0 ;
  • 末尾为 0 ,则直接去掉。

对于每个数字 x 来说,它可以由自己变幻一次,也可以由某些更大的数字变幻得到。
简单的来说,当 x 是偶数时,x 会被 x+1 和 2 * x 变幻一次,当 x 是奇数的时候会被 2 * x 变幻一次。

那么,很容易的发现,如果将奇数和偶数独立开来考虑的话,是满足单调性的。
于是我们考虑对奇数和偶数分别二分确定最大值,然后取两者中的较大值作为答案即可。

首先,我们考虑对于每一个k,可以变幻为 x 的 k ∗ x + b 的最大个数。

k124m
x是奇数x2x,2x+14x,4x+1,4x+2,4x+3m-1+1 个
x是偶数x,x+12x,2x+1,2x+2,2x+34x,4x+1,4x+2…4x+72m-1+1 个

考虑 x 是∈ [1,n],所以对于每一个 k,可以变幻为 x 的最大个数:
x 是 奇 数 : m i n ( n − k ∗ x , k − 1 ) + 1 x 是 偶 数 : m i n ( n − k ∗ x , 2 ∗ k − 1 ) + 1 x 是奇数:min(n- k*x,k-1) + 1\\ x 是偶数:min(n-k*x,2*k-1) + 1 xmin(nkx,k1)+1xmin(nkx,2k1)+1
那么我们就可以得到递归函数:
二次方增长,应该T不了。

ll sol(ll x,ll k)
{
    if(x*k>n)   return 0;
    ll le = n - x*k;
    if(x&1) return min(le,k-1) + 1 + sol(x,k<<1);
    else    return min(le,k*2-1) + 1 + sol(x,k<<1);
}

然后就可以偶数二分,再比较奇数,取最大值即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#include<queue>
#include<utility>
#include<set>
#include<stack>
#include<string>
#include<vector>
#define ll long long
#define llu unsigned long long
using namespace std;
const int maxn = 100010;
ll n;
ll sol(ll x,ll k)
{
    if(x*k>n)   return 0;
    ll le = n - x*k;
    if(x&1) return min(le,k-1) + 1 + sol(x,k<<1);
    else    return min(le,k*2-1) + 1 + sol(x,k<<1);
}
int main(void)
{
    int T;
    ll k;
    scanf("%d",&T);
    while(T--){
        scanf("%lld%lld",&n,&k);
        ll le = 0,ri = (n+2)>>1;
        ll mid;
        while(ri-le>1){
            mid = (le+ri)>>1;
            ll p = sol(mid<<1,1);
            if(p<k)    ri = mid;
            else    le = mid;
        }
        ll lp = sol(le<<1|1,1);
        if(lp>=k)   printf("%lld\n",le<<1|1);
        else    printf("%lld\n",le<<1);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逃夭丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值