2019 GDUT Winter Training III (数位DP\组合数学) 题解
A题
题面:
A - Round Numbers
The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone' (also known as 'Rock, Paper, Scissors', 'Ro, Sham, Bo', and a host of other names) in order to make arbitrary decisions such as who gets to be milked first. They can't even flip a coin because it's so hard to toss using hooves.
They have thus resorted to "round number" matching. The first cow picks an integer less than two billion. The second cow does the same. If the numbers are both "round numbers", the first cow wins,
otherwise the second cow wins.
A positive integer N is said to be a "round number" if the binary representation of N has as many or more zeroes than it has ones. For example, the integer 9, when written in binary form, is 1001. 1001 has two zeroes and two ones; thus, 9 is a round number. The integer 26 is 11010 in binary; since it has two zeroes and three ones, it is not a round number.
Obviously, it takes cows a while to convert numbers to binary, so the winner takes a while to determine. Bessie wants to cheat and thinks she can do that if she knows how many "round numbers" are in a given range.
Help her by writing a program that tells how many round numbers appear in the inclusive range given by the input (1 ≤ Start < Finish ≤ 2,000,000,000).
Input
Line 1: Two space-separated integers, respectively Start and Finish.
Output
Line 1: A single integer that is the count of round numbers in the inclusive rangeStart.. Finish
Sample Input
2 12
Sample Output
6
题面描述:
这个题目讲述的是,现在给定两个数字,问这两个数字之间有多少个数是round number,round number的定义是,这个数用二进制表示下的0的数目大于等于1的数目。
题目分析:
这个题目就是一道典型的数位DP题目,我们把输入的两个数字用二进制表示,分别用start和finish表示,我们分别算出0到start-1的round number的数目以及0到finish的round number的数目,然后用后者减去前者便是答案。
我们对每个二进制位进行枚举,如果这个位选择了数字0的话,那么统计数字0数目的变量加一,否则就是统计数字1数目的变量加一。最后统计完所有的二进制位后,对有效的数字0数目比数字1数目大的方案加一,然后用一个数组来记录这个状态下的方案数,实现记忆化搜索,便于节省时间,提高效率。
值得注意的是,这里的前导0并不算入数字0的统计当中,所以我们需要特别判断前导0的情况即可。
代码:
#include <iostream>
#include <cstdio>
#include <stdio.h>
#include <cstdlib>
#include <stdlib.h>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <string.h>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define reg register
#define ll long long
#define ull unsigned long long
#define bll __int128
#define INF 0x3fffffff
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define lowbit(x) (x&(-x))
using namespace std;
int num[105],dp[105][55][55];
int dfs(int len,bool limit,bool lead,int zero,int one)
{
if (len==0) return zero>=one;
if (!limit && dp[len][zero][one]!=-1) return dp[len][zero][one];
int maxx=limit?num[len]:1;
int cnt=0;
for (reg int i=0;i<=maxx;i++)
{
if (i==0)
{
cnt+=dfs(len-1,i==maxx && limit,lead,lead?zero:zero+1,one);
continue;
}
cnt+=dfs(len-1,i==maxx && limit,0,zero,one+1);
}
return limit?cnt:dp[len][zero][one]=cnt;
}
int solve(int x)
{
memset(num,0,sizeof(num));
int len=0;
while (x)
{
num[++len]=x%2;
x=x>>1;
}
return dfs(len,1,1,0,0);
}
int main()
{
int start,finish;
memset(dp,-1,sizeof(dp));
while (~scanf("%d%d",&start,&finish))
{
printf("%d\n",solve(finish)-solve(start-1));
}
return 0;
}
B题
题面:
B - Balanced Number
A balanced number is a non-negative integer that can be balanced if a pivot is placed at some digit. More specifically, imagine each digit as a box with weight indicated by the digit. When a pivot is placed at some digit of the number, the distance from a digit to the pivot is the offset between it and the pivot. Then the torques of left part and right part can be calculated. It is balanced if they are the same. A balanced number must be balanced with the pivot at some of its digits. For example, 4139 is a balanced number with pivot fixed at 3. The torqueses are 4*2 + 1*1 = 9 and 9*1 = 9, for left part and right part, respectively. It's your job
to calculate the number of balanced numbers in a given range [x, y].
Input
The input contains multiple test cases. The first line is the total number of cases T (0 < T ≤ 30). For each case, there are two integers separated by a space in a line, x and y. (0 ≤ x ≤ y ≤ 10 18).
Output
For each case, print the number of balanced numbers in the range [x, y] in a line.
Sample Input
2
0 9
7604 24324
Sample Output
10
897
题面描述:
这个题目需要我们求在某个区间内平衡数的数目,所谓的平衡数的定义如下,这些平衡数用十进制的表示下,以某个数字位为支点,然后支点左右两边的数字位上的数字分别乘以该数字位与支点的距离的和相等。
题目分析:
我们把输入区间的两个端点的每个数字位用一个数组记录下来,然后最终答案便为0到右端点的平衡数的数目减去0到左端点减一的平衡数数目。
我们需要用一个循环去枚举每个数字位作为支点的情况,然后计算每个数字位上的数字分别乘以该数字位与支点的距离,这里存在一个巧妙的处理,我们如果分别求出支点两边的各个数字位上的数字分别乘以该数字位与支点的距离的总和,然后再进行比较,显然这两个值是不容易求的。
我们定义一个sum变量,记录每个数字位与支点的距离乘以每个数字位的数字的总和。
为了更加明白这个sum变量的用处,下面举一个简单的例子,像4139这个平衡数,我们知道支点是第二位,那么sum=4*(4-2)+1*(3-2)+3*(2-2)+9*(1-2)=0。我们会发现支点两边的每个数字位上的数字乘以该数字位于支点的距离在计算过程中巧妙地互相地进行了相减的运算,所以枚举所有的数字位后,如果sum=0的话,方案数加一,如果在前面的枚举当中,sum小于0的话,我们可以直接退出搜索,因为在往下搜索,sum都不可能为0的。
代码:
#include <iostream>
#include <cstdio>
#include <stdio.h>
#include <cstdlib>
#include <stdlib.h>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <string.h>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define reg register
#define ll long long
#define ull unsigned long long
#define bll __int128
#define INF 0x3fffffff
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define lowbit(x) (x&(-x))
using namespace std;
ll dp[35][25][2005];
int num[105];
ll dfs(int len,bool limit,int pivot,int sum)
{
if (len==0)
{
if (sum==0) return 1;
return 0;
}
if (sum<0) return 0;
if (!limit && dp[len][pivot][sum]!=-1) return dp[len][pivot][sum];
ll cnt=0;
int maxx=limit?num[len]:9;
for (reg int i=0;i<=maxx;i++)
{
cnt+=dfs(len-1,limit && i==maxx,pivot,sum+(len-pivot)*i);
}
return limit?cnt:dp[len][pivot][sum]=cnt;
}
ll solve(ll x)
{
if (x==-1) return 0;
if (x==0) return 1;
memset(num,0,sizeof(num));
int len=0;
while (x)
{
num[++len]=x%10;
x/=10;
}
ll cnt=0;
for (reg int i=1;i<=len;i++)
{
cnt+=dfs(len,1,i,0);
}
return cnt-(len-1);
}
int main()
{
int T;
memset(dp,-1,sizeof(dp));
scanf("%d",&T);
while (T--)
{
ll x,y;
scanf("%lld%lld",&x,&y);
printf("%lld\n",solve(y)-solve(x-1));
}
return 0;
}
C题
题面:
C - F(x)
For a decimal number x with n digits (A nA n-1A n-2 ... A 2A 1), we define its weight as F(x) = A n * 2 n-1 + A n-1 * 2 n-2 + ... + A 2 * 2 + A 1 * 1. Now you are given two numbers A and B, please calculate how many numbers are there between 0 and B, inclusive, whose weight is no more than F(A).
Input
The first line has a number T (T <= 10000) , indicating the number of test cases.
For each test case, there are two numbers A and B (0 <= A,B < 10 9)
Output
For every case,you should output "Case #t: " at first, without quotes. The t is the case number starting from 1. Then output the answer.
Sample Input
3
0 100
1 10
5 100
Sample Output
Case #1: 1
Case #2: 2
Case #3: 13
题面描述:
题目意思就是输入多组数据,然后每组数据都有一个A和B,然后给定一个函数f(x),
分别表示的是数字x的每个数字位上的数字,问你在0到B之间有多少个数的函数值小于等于f(A)。
题目分析:
这个题目如果正常去做的话,我们肯定是对0到B之间枚举所有的数,然后判断这个区间内的每个数的函数值是否小于等于f(A),但是由于题目数据有多组,所以每组数据的A并不一样,因此进行记忆化的数组记录的数据并不正确。
为了解决这个问题,我们只要反过来做即可,我们先把f(A)求出来,然后用f(A)减去从0到B之间枚举出来的每个数的函数值,如果减出来的结果大于等于0的话,证明这个数是可行的,方案数加一,而这时候进行记忆化数组记录的是,剩下的数字位当中所组成的数的函数值小于等于f(A)减去前面数字位乘以相应的二的次幂的方案数,我们只需要判断枚举完所有数字位减出来的结果是否为0,如果为0的话就是满足要求的数,经过这样的处理,无论A为何值,进行记忆化的数组记录的方案数均不受影响。
代码:
#include <iostream>
#include <cstdio>
#include <stdio.h>
#include <cstdlib>
#include <stdlib.h>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <string.h>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define reg register
#define ll long long
#define ull unsigned long long
#define bll __int128
#define INF 0x3fffffff
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define lowbit(x) (x&(-x))
using namespace std;
int sumA,em[15],num[15],dp[15][20005];
int qsumA(int n)
{
int len=0,ans=0;
while (n)
{
ans+=(n%10)*em[len];
n/=10;
len++;
}
return ans;
}
ll dfs(int len,bool limit,int sum)
{
if (len==0) return sum>=0;
if (sum<0) return 0;
if (!limit && dp[len][sum]!=-1) return dp[len][sum];
int maxx=limit?num[len]:9;
int cnt=0;
for (reg int i=0;i<=maxx;i++)
{
cnt+=dfs(len-1,limit && i==maxx,sum-em[len-1]*i);
}
return limit?cnt:dp[len][sum]=cnt;
}
ll solve(ll x)
{
memset(num,0,sizeof(num));
int len=0;
while (x)
{
num[++len]=x%10;
x/=10;
}
return dfs(len,1,sumA);
}
i