引自:http://wenku.baidu.com/view/d2414ffe04a1b0717fd5dda8.html
题目大意:
求给定区间[X,Y]中满足下列条件的整数个数: 这个数恰好等于 K 个互不相等的 B 的整数次幂之和。例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:17 = 2^4 + 2^0;
18 = 2^4 + 2^1;
20 = 2^4 + 2^2;
分析:
所求的数为互不相等的幂之和,亦即其 B 进制表示的各位数字都只能是 0 和 1。因此,我们只需讨论二进制的情况,其他进制都可以转化为二进制求解。
很显然,数据范围较大,不可能采用枚举法,算法复杂度必须是 log(n)级别,因此我们要从数位上下手。
本题区间满足区间减法, 因此可以进一步简化问题: 令 count[i..j]表示[i..j]区间内合法数的个数,则 count[i..j]=count[0..j]-count[0..i-1]。换句话说,给定 n,我们只需求出从 0 到 n有多少个符合条件的数。
假设 n=13,其二进制表示为 1101,K=3。我们的目标是求出 0 到 13 中二进制表示含 3个 1 的数的个数。为了方便思考,让我们画出一棵高度为 4 的完全二叉树
为了方便起见,树的根用 0 表示。这样,这棵高度为 4 的完全二叉树就可以表示所有 4位二进制数(0..24-1) ,每一个叶子节点代表一个数。
其中,红色路径表示 n。所有小于 n 的数组成了三棵子树,分别用蓝色、绿色、紫色表示。因此,统计小于 13 的数,就只需统计这三棵完整的完全叉树: 统计蓝子树内含 3 个 1 的数的个数、 统计绿子树内含 2 个 1 的数的个数 (因为从根到此处的路径上已经有 1 个 1) , 以及统计紫子树内含 1 个 1 的数的个数。
注意到, 只要是高度相同的子树统计结果一定相同。 而需要统计的子树都是“右转” 时遇到的。当然,我们不能忘记统计 n 本身。实际上,在算法最初时将 n 自加 1,可以避免讨论 n本身,但是需要注意防止上溢。
剩下的问题就是,如何统计一棵高度为 i 的完全二叉树内二进制表示中恰好含有 j 个 1的数的个数。 这很容易用递推求出:设 f[i,j]表示所求, 则分别统计左右子树内符合条件数的个数,f[i,j]=f[i-1,j]+f[i-1,j-1]。
这样,我们就得出了询问的算法:首先预处理 f,然后对于输入 n,我们在假想的完全二叉树中, 从根走到 n 所在的叶子, 每次向右转时统计左子树内数的个数。
下面是 C++代码
void init()//预处理
{
f[0][0]=1;
for (int i=1; i<=31; ++i) {
f[i][0]=f[i-1][0];
for (int j=1; j<=i; ++j) f[i][j]=f[i-1][j]+f[i-1][j-1];
}
}
int calc(int x,int k)//统计[0..x]内二进制表示含k个1的数的个数
{
int tot=0,ans=0;//tot记录当前路径上已有的1的数量,ans表示答案
for (int i=31; i>0; --i) {
if (x&(1<<i)) {
++tot;
if (tot>k) break;
x=x^(1<<i);
}
if ((1<<(i-1))<=x) {
ans+=f[i-1][k-tot];
}
}
if (tot+x==k) ++ans;
return ans;
}
最后的问题就是如何处理非二进制。 对于询问 n, 我们需要求出不超过 n 的最大 B 进制
表示只含 0、1 的数:找到 n 的左起第一位非 0、1 的数位,将它变为 1,并将右面所有数位
设为 1。将得到的 B 进制表示视为二进制进行询问即可。
预处理递推 f 的时间复杂度为 O(log2(n)),共有 O(log(n))次查询,因此总时间复杂度为O(log2(n))。
实际上, 最终的代码并不涉及树的操作, 我们只是利用图形的方式来方便思考。 因此也
可以只从数位的角度考虑:对于询问 n,我们找到一个等于 1 的数位,将它赋为 0,则它右
面的数位可以任意取,我们需要统计其中恰好含有 K-tot 个 1 的数的个数(其中 tot 表示这
一位左边的 1 的个数) ,则可以利用组合数公式求解。逐位枚举所有”1”进行统计即可。
我们发现,之前推出的 f 正是组合数。同样是采用“逐位确定”的方法,两种方法异曲
同工。当你觉得单纯从数位的角度较难思考时,不妨画出图形以方便思考。
代码如下:
const int M = 55;
int f[M][M];
void init()
{
f[0][0] = 1;
for(int i = 1; i <=31; ++i) {
f[i][0] = f[i-1][0];
for(int j = 1; j <= i; ++j)
f[i][j] = f[i-1][j]+f[i-1][j-1];
}
}
int trans(int x, int b)
{
int bety[50], cnt = 0, ans = 0;
while(x) {
bety[cnt++] = x % b;
x /= b;
}
for(int i = cnt-1; i >= 0; --i) {
if(bety[i]>1) {
for(int j = i; j >= 0; --j)
ans |= (1 << j);
break;
} else ans |= (bety[i] << i);
}
return ans;
}
int cal(int x, int k)
{
int cnt = 0, ans = 0;
for(int i = 31; i > 0; --i) {
if(x&(1<<i)) {
++cnt;
if(cnt>k) break;
x ^= (1<<i);
}
if((1<<(i-1))<=x)
ans += f[i-1][k-cnt];
}
if(cnt+x == k) ++ans;
return ans;
}
int main()
{
int x, y, k, b;
init();
while(~scanf("%d%d%d%d", &x, &y, &k, &b)) {
printf("%d\n", cal(trans(y,b),k)-cal(trans(x-1,b),k));
}
return 0;
}