刘聪的论文<<浅谈数位类统计问题>>中第三例
这篇文章上思路已经分析的非常清楚了,只不过没给出具体代码,不过我还是花了很久才写出来......
原题:http://www.spoj.com/problems/SORTBIT/
题意:所有的数按照二进制中含1的个数从小到大排序,若个数相同按数的大小排,然后给我们一个区间(m,n) 求区间(m,n)中第k大的数并输出 (负数以补码的形式表示)
通过<<浅谈数位类统计问题>>第一例cal(a,k)l函数我们可以得到 [0,a]中含有k个1的组合数(用排列组合也可以算,他这用位运算统计).
然后我们只要
int s=k;
for(i=1;i<=31;i++)
{
int t=cal(n,i)-cal(m-1,i);
if(s-t<=0)
break;
s-=t;
}
可以得到第k大的数是在i个1中的第s位
采用二分查找出s位的值输出就可以了
若区间在负半轴 根据补码的性质 将数的最高位等于0 就转换成了正数 因为数以1的个数排序 同时去一位1,排序顺序不变
得到的值在最高位再加回1就可以了
注意边界值为0时的特判
#include<iostream>
using namespace std;
typedef long long LL;
int dp[32][32];
void init()
{
for(int i=0;i<=31;i++)
{
dp[i][0]=1;
for(int j=1;j<=i;j++)
dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
}
}
int cal(int a,int k)
{
int ans,tot;
ans=tot=0;
for(int i=31;i>0;i--)
{
if((1<<i)&a)
{
tot++;
if(tot>k)
break;
a^=1<<i;
}
if(1<<(i-1)<=a)
ans+=dp[i-1][k-tot];
}
if(tot+a==k)
ans++;
return ans;
}
int slove(int m,int n,int k)
{
int s;
int i;
s=k;
for(i=1;i<=31;i++)
{
int t=cal(n,i)-cal(m-1,i);
if(s-t<=0)
break;
s-=t;
}
int l,r,mid;
l=m;
r=n;
while(l<r)
{
mid=((LL)l+(LL)r)>>1; //避免溢出
int t=cal(mid,i)-cal(m-1,i);
if(t<s)
l=mid+1;
else
r=mid;
}
return l;
}
int main()
{
init();
int cnt;
cin>>cnt;
while(cnt--)
{
int m,n,k;
cin>>m>>n>>k;
if(m==0&&n==0)
{
cout<<0<<endl;
continue;
}
if(m>=0)
{
if(m==0)
{
m++;
k--;
}
int ans=slove(m,n,k);
cout<<ans<<endl;
}
else
{
if(n==0)
{
n--;
k--;
}
m^=1<<31;
n^=1<<31;
int ans=slove(m,n,k);
ans|=1<<31;
cout<<ans<<endl;
}
}
// system("pause");
return 0;
}