[CF1271E] Common Number - 二分,数学

定义 \(f(x)=[x\bmod 2=1](x-1) + [x\bmod 2=0] (x/2)\)

对于任意 \(x\),不断令 \(x=f(x)\) 进行迭代,最终会得到 \(x=1\),定义 \(path_x\)\(x\) 在这个迭代过程中出现过的所有值的集合

给定 \(n,k\),求最大的 \(y\),使得有至少 \(k\)\(i \in [1,n]\) 使得 \(y \in path_i\)

Solution

难度:L5
以下对于数的讨论都是在二进制意义下进行的

\(f(i)\) 表示 \(i\) 出现在 \([1,n]\) 多少个数的 \(path\) 中,设 \(g(i)\) 表示 \(i\)\([1,n]\) 中多少个数二进制意义下的前缀

  • 对于奇数 \(i\)\(f(i)=g(i)\)
  • 对于偶数 \(i\)\(f(i)=g(i)+g(i+1)\)

由于 \(f\) 序列的奇数项和偶数项分别单调,于是我们分别二分,找到最后一个 \(f(i) \geq k\) 的奇数项和偶数项即可

考虑如何计算 \(g(i)\),首先我们找到一个最小的长度 \(len\),使得 \(i2^{len+1} >n\),然后二分找到 \(mid\) 使得 \(i 2^{len}+mid \leq n\),显然可以二分得到,那么 \(g(i)=mid+2^{len}\)

总体复杂度 \(O(\log^2 n)\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
int n,k;

int g(int i) {
    if(i==0) return 0; //!
    int l=0,r=n,len=0;
    while(i*(1ll<<(len+1))<=n) ++len;
    r=1ll<<len;
    while(l<r) {
        int mid=(l+r)/2;
        int t=mid;
        if(i*(1ll<<len)+mid<=n) l=mid+1;
        else r=mid;
    }
    int mid=l-1;
    int t=mid;
    //cout<<"calc "<<i<<" "<<mid<<" "<<len<<endl;
    return mid+(1ll<<len);
}

int f(int i) {
    if(i&1) return g(i);
    return g(i)+g(i+1);
}

int sol1() {
    int l=0,r=n;
    while(l<r) {
        int mid=(l+r)/2;
        int xmid = mid*2;
        if(f(xmid)>=k) l=mid+1;
        else r=mid;
    }
    return (l-1)*2;
}

int sol2() {
    int l=0,r=n;
    while(l<r) {
        int mid=(l+r)/2;
        int xmid = mid*2+1;
        if(f(xmid)>=k) l=mid+1;
        else r=mid;
    }
    return (l-1)*2+1;
}

signed main() {
    //ios::sync_with_stdio(false);
    cin>>n>>k;
    cout<<max(sol1(),sol2());
}

猜你喜欢

转载自www.cnblogs.com/mollnn/p/12498655.html