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 的最大个数。
k | 1 | 2 | 4 | m |
---|---|---|---|---|
x是奇数 | x | 2x,2x+1 | 4x,4x+1,4x+2,4x+3 | m-1+1 个 |
x是偶数 | x,x+1 | 2x,2x+1,2x+2,2x+3 | 4x,4x+1,4x+2…4x+7 | 2m-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
x是奇数:min(n−k∗x,k−1)+1x是偶数:min(n−k∗x,2∗k−1)+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;
}