您的位置:首页 > 产品设计 > UI/UE

SPOJ-1182 Sorted bit squence 数位DP

2012-08-14 10:32 453 查看
这题虽然说是什么按位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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: