1. 程式人生 > >UVALive 3675 Sorted bit sequence(數位dp+二分)

UVALive 3675 Sorted bit sequence(數位dp+二分)

題目連結

題意

將區間[L,R]內的所有整數按照其二進位制表示中1 的數量從小到大排序。如果 1 的數量 相同,則按照數的大小排序。求這個序列中的第K個數。其中,負數使用補碼來表示:一個負數的二進位制表示與其相反數的二進位制之和恰好等於232
資料規模:L×R0231LR23111Kmin(RL+1,2147473547)

分析

參考論文中的分析方法。
首先注意到一個條件LR0
我們先考慮mn同為正數的情況。
由於排序的第一關鍵字是1的數量,第二關鍵字是數的大小,因此我們很容易確定答案中1的個數:依次統計區間[m,n]內二進位制表示中含 1的數量為 0,1,2,…的數,直到累加的答案超過K

,則當前值就是答案含 1 的個數,假設是s。這個數位dp可以解決,列舉需要統計的1的個數,dfs
同時,我們也求出了答案是第幾個[m,n]中含 s個 1 的數。因此,只需二分答案,求出[L,ans] 中含 s 個 1 的數的個數進行判斷即可。

對於L<0,R<0的情況,我是把區間變為正的區間,根據一個負數的二進位制表示與其相反數的二進位制之和恰好等於232,最後再把結果變回來就好了。

需要特殊處理L=0R=0的情況。

因為LR0了,所以實際上最麻煩的討論:L<0,R>0的情況已經避免掉了。

Code

//https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&category=245&page=show_problem&problem=1676
#include <stdio.h> #include <string.h> #include <algorithm> #include <math.h> using namespace std; typedef long long ll; const ll base = 1ll << 32; int T, digit[35]; ll L, R, K, LL, RR; ll dp[35][35][35], cnt[4][35]; ll dfs(int pos, int pre, int limit, int sum) { if
(pos == -1) return pre == sum; if (pre > sum) return 0; if (!limit && dp[pos][pre][sum] != -1) return dp[pos][pre][sum]; int last = limit ? digit[pos] : 1; ll ret = 0; for (int i = 0; i <= last; ++i) { ret += dfs(pos - 1, pre + i, limit && (i == last), sum); } if (!limit) dp[pos][pre][sum] = ret; return ret; } ll solve(ll x, int id, int flag) { memset(digit, 0, sizeof (digit)); int len = 0; // printf("x = %lld\n", x); while (x) { digit[len++] = x % 2; x /= 2; } if (flag != -1) { return dfs(len - 1, 0, 1, flag); } memset(cnt[id], 0, sizeof(cnt[id])); for (int i = 1; i <= 32; ++i) { // 別忘了32! cnt[id][i] = dfs(len - 1, 0, 1, i); // 列舉有i個1 // printf("cnt[%d][%d] = %lld\n", id, i, cnt[id][i]); } return 0; } void work(int flag) { solve(LL, 0, -1); solve(RR, 1, -1); ll prefix = 0; int goal; for (int i = 1; i <= 32; ++i) { // 別忘了32! prefix += (cnt[1][i] - cnt[0][i]); if (prefix >= K) { prefix -= (cnt[1][i] - cnt[0][i]); goal = i; break; } } // printf("goal = %d\n", goal); ll left = K - prefix + cnt[0][goal]; ll high = RR, low = LL, mid; while (low < high) { mid = (1ll * low + high) / 2; ll tmp = solve(mid, -1, goal); if (tmp < left) low = mid + 1; else high = mid; } ll ans = high; if (flag) ans = -(base - high); printf("%lld\n", ans); } int main() { memset(dp, -1, sizeof(dp)); scanf("%d", &T); while (T--) { // 注意n * m >= 0 scanf("%lld%lld%d", &L, &R, &K); if (L < 0 && R < 0) { LL = base + L - 1, RR = base + R; work(1); } else if (L < 0 && R == 0) { if (K == 1) printf("0\n"); else { K--; LL = base + L - 1, RR = base - 1; work(1); } } else if (L == 0 && R == 0) { // K = 0 printf("0\n"); } else if (L == 0 && R > 0) { if (K == 1) printf("0\n"); else { K--; LL = 0, RR = R; work(0); } } else { // L > 0 && R > 0 LL = L - 1, RR = R; work(0); } } return 0; }