题意
将区间 \([m,n]\) 的所有整数按照其二进制位表示的数中 \(1\) 的数量从小到大排序。如果 \(1\) 的数量相同,则按照数的大小排序。求序列中第 \(k\) 个数。
其中,负数使用补码来表示:一个负数的二进制数与其相反数的二进制数之和恰好等于 \(2^{32}\)。
分析
考虑用 uint32_t
存储负数,这样得到的就是二进制数就是符合题目描述的补码。
根据条件 \(m\times n\ge 0\) 得到 \(m,n\) 是符号相同的,那么将 \([m,n]\) 区间内的数转换成 uint32_t
后在值域上是连续的。(除了 \(0\),所以需要对 \(0\) 进行特判)
先讨论第一个排序条件:按照其二进制位表示的数中 \(1\) 的数量从小到大排序。
考虑实现这样一种操作:查询区间 \([l,r]\) 内二进制下 \(1\) 的个数为 \(c\) 的数的个数。
显然在左端点固定的情况下随着右端点增加操作的结果是单调不降的。
从 \(1\) 到 \(32\) 依次枚举 \(c\),得到排序后第 \(k\) 个数二进制下 \(1\) 的个数 \(c'\) 以及排序后第 \(k\) 个数在区间 \([l,r]\) 内二进制下 \(1\) 的个数为 \(c'\) 的数中的排名 \(k'\)。
这一部分说的有点抽象,建议结合代码理解。
得到了第 \(k\) 个数二进制下 \(1\) 的个数以及排名 \(k'\) 后,根据第二个排序条件,考虑在 \([m,n]\) 中二分。
查找第一个 \(i\) 使得区间 \([l,i]\) 内二进制下 \(1\) 的个数为 \(c\) 的数等于 \(k'\)。
在 int
下输出这个数即可。
现在我们只需要实现这个操作。
发现这个操作比较典型,使用数位 dp 解决。
时间复杂度 \(O(T\log^3n)\)。
code
#include<bits/stdc++.h>
using namespace std;
int dp[40][40][2];
typedef bitset<32> uint_t;
uint32_t dfs(int pos, int cnt, int num, const uint_t &upr, bool up, int tcnt)
{
if(pos==-1) return cnt==tcnt;
if(cnt>tcnt) return 0;
if(!up&&dp[pos][tcnt-cnt][num]!=-1)
return dp[pos][tcnt-cnt][num];
uint32_t ret=0;
int mx=up?upr[pos]:1;
for(int i=0;i<=mx;i++)
ret+=dfs(pos-1, cnt+i, i, upr, up&&i==upr[pos], tcnt);
if(!up) dp[pos][tcnt-cnt][num]=ret;
return ret;
}
uint32_t count_bit(uint32_t l, uint32_t r, int k)
{
uint_t bl=l-1, br=r;
if(!l) return dfs(31, 0, 0, br, 1, k);
return dfs(31, 0, 0, br, 1, k)-dfs(31, 0, 0, bl, 1, k);
}
void solve()
{
int64_t l, r, k;
cin>>l>>r>>k;
if((!l||!r)&&k==1) return cout<<0, void();
uint32_t L=l, R=r;
if(l<0&&!R) R=UINT32_MAX, k--;
if(r>0&&!L) L++, k--;
int s=1;
for(;;s++)
{
uint32_t v=count_bit(L, R, s);
if(v>=k) break;
k-=v;
}
l=L-1, r=R;
while(l+1<r)
{
uint32_t mid=(l+r)>>1;
if(count_bit(L, mid, s)<k) l=mid;
else r=mid;
}
cout<<(int)r<<'\n';
}
int main()
{
memset(dp, -1, sizeof dp);
int t;
cin>>t;
while(t--) solve();
}
标签:int,题解,SP1182,个数,二进制,squence,tcnt,排序,uint32
From: https://www.cnblogs.com/redacted-area/p/18379562