ural 1057 - Amount of Degrees(数位统计)
2013-10-27 11:16
260 查看
引自: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 的完全二叉树
![](http://img.blog.csdn.net/20131027111755437?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcHJpbW9ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
为了方便起见,树的根用 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++代码
最后的问题就是如何处理非二进制。 对于询问 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 正是组合数。同样是采用“逐位确定”的方法,两种方法异曲
同工。当你觉得单纯从数位的角度较难思考时,不妨画出图形以方便思考。
代码如下:
题目大意:
求给定区间[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; }
相关文章推荐
- [ACM] ural 1057 Amount of degrees (数位统计)
- URAL 1057 Amount of Degrees(数位统计)
- [ACM] ural 1057 Amount of degrees (数位统计)
- URAL 1057 Amount of Degrees (数位统计)
- ural1057 Amount of Degrees数位统计入门题
- ural 1057 Amount of Degrees(数位统计)
- Ural 1057 Amount of Degrees(数位DP)
- ural1057 Amount of Degrees 数位dp
- Ural1057 - Amount of Degrees(数位DP)
- ural 1057 Amount of Degrees(数位dp)
- [ural1057]Amount of Degrees && 数位DP
- URAL 1057 Amount of Degrees(数位dp)
- URAL 1057 Amount of Degrees (数位DP)
- ural 1057 Amount of degrees (数位dp)
- [ural1057][Amount of Degrees] (数位dp+进制模型)
- ural1057 Amount of Degrees ——数位DP
- 【数位DP】URAL 1057 Amount of Degrees
- ural1057 Amount of Degrees 【数位dp】论文例题
- URAL 1057 Amount of Degrees (数位dp)
- URAL 1057 Amount of Degrees 数位DP *