SPOJ-1182 Sorted bit squence 数位DP

这题虽然说是什么按位DP,其实尼玛不是组合数学么。不过硬是用模板的按位DP实现了,其实也就是记忆化搜索,本题恶心就在于有负数的存在,其实对付它就是把正数的第33位都变成1,用long long来处理,这样既保证了负数小于正数,又可以化成单一的区间了。在按位统计的时候记得当1出现在33位的时候不统计这个1。这题思路也就是先把[a, b]区间内含有一个1,两个1,三个1...的数的个数全部统计出来,一个for循环就能够找出该数字出现在有几个1的一段上,然后再二分查找答案,最后输出。

代码如下:

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef unsigned int Int;

Int a, b;

int bit[35], sum[35], dp[35][35], k; // 如何用记忆化去优化搜索
// sum[i]表示i个1的数有多少个
// dp[i][j] 代表剩余i位在没有约束的条件下产生j个1的组合数

long long x, y, ret;

void aly(int pos, int statu, int limit, int sign)
{
    if (pos == -1) {
        sum[statu] += sign;
        return;
    }
    if (!limit && dp[pos][0] != -1) { // dp[i][0] - dp[i][32]是成套更新的 
        for (int i = 0; i <= 32; ++i) {
            sum[i+statu] += sign * dp[pos][i];
        }
        return;
    }
    int s, end = limit ? bit[pos] : 1;
    if (!limit) {
        for (int i = 0; i <= 32; ++i) {
            dp[pos][i] = sum[i]; // 先记录    
        }
    }
    for (int i = 0; i <= end; ++i) {
        s = statu;
        if (i == 1 && pos != 32) s = statu + 1;  // 只需要对状态更新进行判定,最高位(32位)为虚位,不需要计算 
        aly(pos-1, s, limit && i == end, sign);
    }
    if (!limit) {
        for (int i = 0; i <= 32; ++i) {
            dp[pos][i] = abs(sum[i+statu] - dp[pos][i]);    // 可能带来负值,计算出增加的值 
        }
    }
}

void Cal(long long z, int sign)
{
    int idx = -1;
    while (z) {
        bit[++idx] = z % 2;
        z /= 2;    
    }
    aly(idx, 0, 1, sign);
}

Int Ac(long long z, int loc, int Len)
{
    int idx = -1;
    memset(sum, 0, sizeof (sum));
    Cal(z, 1), Cal(x-1, -1);
    return sum[Len] >= loc;
}

long long bsearch(long long l, long long r, int loc, int Len) // 有Len个1的,第loc个数 
{
    long long mid, ret;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (Ac(mid, loc, Len)) {
            ret = mid;
            r = mid - 1;
        }
        else {
            l = mid + 1;    
        }
    }
    return ret;
}

int main()
{
    memset(dp, 0xff, sizeof (dp));
    int T;
    scanf("%d", &T);
    while (T--) {
        memset(sum, 0, sizeof (sum));
        x = y = 0;
        scanf("%u %u %u", &a, &b, &k);
        x |= a, y |= b;
        if (int (b) >= 0) {
            y += 1LL << 32; // 给所有的正数加上一位的虚位 
        }
        if (int (a) >= 0) {
            x += 1LL << 32;
        }
        Cal(y, 1), Cal(x-1, -1);  // 已经统计出了区间内含有各个1的数字的数量
        for (int i = 0; i <= 32; ++i) {
            k -= sum[i];
            if (k <= 0) {
                k += sum[i]; // 去区间内寻找第k个位数为i的数字
                ret = bsearch(x, y, k, i);
                printf("%d\n", int (ret));
                break;
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值