https://www.acwing.com/problem/content/description/1083/
度的数量
求给定区间 [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B
的整数次幂之和。
例如,设 X=15,Y=20,K=2,B=2
,则有且仅有下列三个数满足题意:
17=24+20
18=24+21
20=24+22
输入格式
第一行包含两个整数 X
和 Y,接下来两行包含整数 K 和 B
。
输出格式
只包含一个整数,表示满足条件的数的个数。
数据范围
1≤X≤Y≤231−1
,
1≤K≤20,
2≤B≤10
输入样例:
15 20
2
2
输出样例:
3
难度: 中等 |
时/空限制: 1s / 64MB |
总通过数: 195 |
总尝试数: 382 |
来源: 《信息学奥赛一本通》 , Ural 1057 |
算法标签 |
将问题放在B进制意义下考虑,即求在[l, r]的区间内有K个位是1,其余位是0的数。
求得[0, r]满足的数为S(r)个,[0, l - 1]满足的数为S(l - 1)个,最后答案即为S(r) - S(l - 1)。
求S(x)的方法如下:
1,数位DP。
设f[i][j]表示在最高位到第i位这一段中,使用了j个1,且小于X在这一段表示的数,的所有方案数。
g[i][j]表示在最高位到第i位这一段中,使用了j个1,且等于X在这一段表示的数(即这一段的数字完全相同),的所有方案数(显然要么为0,要么为1)。
分情况讨论转移即可。
最后答案为f[1][K] + g[1][K]。
#include
#include
#include
#define f(i,l,r) for(i=(l);i<=(r);i++)
#define ff(i,r,l) for(i=(r);i>=(l);i--)
using namespace std;
const int MAXN = 40;
int l, r, K, B;
int f[MAXN][MAXN], g[MAXN][MAXN];
int a[MAXN];
int work(int x)
{
memset(f, 0, sizeof f);
memset(g, 0, sizeof g);
int i, j, ans, n = 0;
while(x){
a[++n] = x % B;
x /= B;
}
g[n + 1][0] = 1;
f[n + 1][0] = 0;
ff(i, n, 1){
f[i][0] = 1;
if(!a[i]) g[i][0] = g[i + 1][0];
f(j, 1, min(K, n - i + 1)){
f[i][j] = f[i + 1][j] + f[i + 1][j - 1];
if(a[i]){
f[i][j] += g[i + 1][j];
if(a[i] > 1){
f[i][j] += g[i + 1][j - 1];
}
else{
g[i][j] = g[i + 1][j - 1];
}
}
else{
g[i][j] = g[i + 1][j];
}
}
}
return f[1][K] + g[1][K];
}
int main()
{
int i;
cin >> l >> r >> K >> B;
cout << work(r) - work(l - 1) << endl;
return 0;
}
2,记忆化搜索。
搜索时记录当前在哪一位,使用了几个1,当前这一段是否小于原数。
#include
#include
#include
#define f(i,l,r) for(i=(l);i<=(r);i++)
#define ff(i,r,l) for(i=(r);i>=(l);i--)
using namespace std;
const int MAXN = 40;
int l, r, K, B;
int f[MAXN][MAXN][2];
int a[MAXN];
int dfs(int cur, int num, int tag)
{
int &v = f[cur][num][tag];
if(~ v) return v;
if(cur < K - num || num > K) return v = 0;
if(cur == 0 && num == K) return v = 1;
v = 0;
if(a[cur]){
v += dfs(cur - 1, num, tag | 1);
if(a[cur] > 1){
v += dfs(cur - 1, num + 1, tag | 1);
}
else{
v += dfs(cur - 1, num + 1, tag);
}
}
else{
v += dfs(cur - 1, num, tag);
if(tag){
v += dfs(cur - 1, num + 1, tag);
}
}
return v;
}
int work(int x)
{
memset(f, -1, sizeof f);
int i, j, ans, n = 0;
while(x){
a[++n] = x % B;
x /= B;
}
return dfs(n, 0, 0);
}
int main()
{
int i;
cin >> l >> r >> K >> B;
cout << work(r) - work(l - 1) << endl;
return 0;
}
3,组合计数。
考虑最高位。
1,若最高位小于原数,则其余位1的位置可以任意放,方案位c(n,m)(剩余有n位,要放m个1)
2,若最高位等于原数,则移到次高位,递归进行这两个步骤。
#include
#include
#include
#define f(i,l,r) for(i=(l);i<=(r);i++)
#define ff(i,r,l) for(i=(r);i>=(l);i--)
using namespace std;
const int MAXN = 40;
int l, r, K, B;
int f[MAXN][MAXN];
int C[MAXN + 1][MAXN + 1];
int a[MAXN];
void init()
{
int i, j;
f(i, 0, MAXN){
C[i][0] = 1;
f(j, 1, i){
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
}
}
int work(int x)
{
init();
int i, j, ans, n = 0;
while(x){
a[++n] = x % B;
x /= B;
}
int res = 0, cnt = 0;
ff(i, n, 1){
if(a[i]){
res += C[i - 1][K - cnt];
if(a[i] > 1){
if(K - cnt - 1 > 0){
res += C[i - 1][K - cnt - 1];
}
break;
}
else{
cnt ++;
if(cnt > K){
break;
}
}
}
}
if(i == 0 && cnt == K) res ++;
return res;
}
int main()
{
int i;
cin >> l >> r >> K >> B;
// cout << work(r) << endl;
cout << work(r) - work(l - 1) << endl;
return 0;
}