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
1≤k≤min(R−L+1,2147473547),−231≤L,R≤231−1。
题目还有一个条件:
L
×
R
≥
0
L\times R \geq 0
L×R≥0。
解析:
参考刘聪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(L−1,one)≥k′。
注意需要特判
0
0
0:if(0) --k;
当都是非负数时:特判
L
=
0
L=0
L=0时,
L
−
1
L-1
L−1为负数的情况,
f
(
L
−
1
,
o
n
e
)
=
0
f(L-1,one)=0
f(L−1,one)=0。
当都是非正数时:特判
L
=
−
2147483648
L=-2147483648
L=−2147483648,
L
−
1
L-1
L−1溢出了,
f
(
L
−
1
,
o
n
e
)
=
0
f(L-1,one)=0
f(L−1,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;
}