UVALive - 3675 Sorted bit sequence 二分+数位dp

UVALive - 3675:

题目链接

题意:

t ( 20 ) t(20) t(20)组数据询问 [ L , R ] [L,R] [L,R]区间内数字排序后的第 k k k大数字是多少?
排序规则:若数字的二进制补码中 1 1 1的个数不同,则按个数从小到大排序;若相同,则按十进制大小从小到大排序。
1 ≤ k ≤ m i n ( R − L + 1 , 2147473547 ) , − 2 31 ≤ L , R ≤ 2 31 − 1 1\leq k \leq min(R-L+1,2147473547), -2^{31}\leq L,R\leq 2^{31}-1 1kmin(RL+1,2147473547)231L,R2311
题目还有一个条件: L × R ≥ 0 L\times R \geq 0 L×R0

解析:

参考刘聪dalao的论文:《浅谈数位类统计问题》,需要论文的可以邮箱问我要。

按照论文的思路,你可以利用数位dp很轻松的算出来: f ( x , m ) = f(x,m)= f(x,m)=小于等于 x x x的数中二进制形式恰好包含 m m m 1 1 1的数的个数。

我们可以枚举答案的二进制形式中 1 1 1的个数 o n e one one。并累加直到数量大于等于 k k k为止。

那么答案的二进制包含 o n e one one 1 1 1。然后在 [ L , R ] [L,R] [L,R]里二分 m i d mid mid,找到最小的 m i d mid mid满足 f ( m i d , o n e ) − f ( L − 1 , o n e ) ≥ k ′ f(mid,one)-f(L-1,one)\geq k' f(mid,one)f(L1,one)k

注意需要特判 0 0 0if(0) --k;

当都是非负数时:特判 L = 0 L=0 L=0时, L − 1 L-1 L1为负数的情况, f ( L − 1 , o n e ) = 0 f(L-1,one)=0 f(L1,one)=0
当都是非正数时:特判 L = − 2147483648 L=-2147483648 L=2147483648 L − 1 L-1 L1溢出了, f ( L − 1 , o n e ) = 0 f(L-1,one)=0 f(L1,one)=0

数位dp的时候不管他是正数还是负数,全都当成 32 32 32为无符号类型的数进行数位dp,这样就不用管正负的影响了。这时数的二进制分解,用 b i t s e t bitset bitset就行了。

AC代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define iis std::ios::sync_with_stdio(false)
#define pb push_back
#define o2(x) (x)*(x)
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
inline LL read(){
  LL x=0;int f=0;char ch=getchar();
  while (ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
  while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
  return x=f?-x:x;
}
const int INF = 0x3f3f3f3f;
const LL mod = 998244353;
const int MXN = 1e4 + 7;

int ar[MXN], br[MXN];
LL dp[35][10][75];//第i位为j,后i-1位共有k个1的方案数
LL dfs(int pos,int cur,bool lead,bool limit,int now,int nd,int *ar) {
    if(pos == -1) return now == nd;
    if(now > nd) return 0;
    if(!limit&&!lead&&dp[pos][cur][nd-now]!=-1) return dp[pos][cur][nd-now];
    int up = limit? ar[pos]: 1;
    LL sum = 0;
    for(int i = 0; i <= up; ++i) {
        sum += dfs(pos-1,i,lead&&i==0,limit&&i==ar[pos],now+(i==1), nd, ar);
    }
    if(!limit&&!lead) dp[pos][cur][nd-now] = sum;
    return sum;
}
LL check(LL y, int one) {
    bitset<32> B(y);
    string b = B.to_string();
    reverse(b.begin(), b.end());
    for(int i = 0; i < 32; ++i) br[i] = b[i]-'0';
    return dfs(31, 1, 1, 1, 0, one, br);
}
LL solve(LL x, LL y, LL k, int ip) {
    int flag = 1;
    //特判0和溢出
    if(ip == 1) {//非负数
        if(x == 0 && k == 1) return 0;
        if(x == 0) --k;
        if(x == 0) flag = 0;
    }else {//非正数
        if(y == 0 && k == 1) return 0;
        if(y == 0) --k;
        if(x == -2147483648) flag = 0;
    }
    LL tx = x, ty = y;
    int p1 = 32, p2 = 32, one = 1; -- x;
    bitset<32> A(x), B(y);//二进制分解
    string a = A.to_string(), b = B.to_string();
    reverse(a.begin(), a.end()); reverse(b.begin(), b.end());
    for(int i = 0; i < 32; ++i) ar[i] = a[i]-'0',br[i] = b[i]-'0';
    LL sum = 0, L = tx, R = ty, mid, ans = 0, tmp = 0, last = 0;
    for(int i = 1; i <= 32; ++i) {//枚举答案中1的个数
        one = i;
        if(flag) {
            tmp = dfs(p1-1, 1, 1, 1, 0, i, ar);
            sum += dfs(p2-1, 1, 1, 1, 0, i, br) - tmp;
        }else {
            tmp = 0;
            sum += dfs(p2-1, 1, 1, 1, 0, i, br);
        }
        if(sum >= k) break;
        last = sum;
    }
    k -= last;//减去前面那些数
    while(L <= R) {
        mid = (L+R) >> 1;
        if(check(mid, one) - tmp >= k) R = mid-1, ans = mid;
        else L = mid+1;
    }
    return ans;
}
//x * y >= 0
int main(int argc, char const *argv[]) {
    memset(dp, -1, sizeof(dp));
    int tim; scanf("%d", &tim);
    while(tim --) {
        LL x = read(), y = read(), k = read(), ans;
        ans = solve(x, y, k, x >= 0);
        printf("%d\n", (int)ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值