第一题:数的3进制表示
在线测评链接:http://121.196.235.151/p/P1041
题目描述
ak机是一个研究生,他发现科研实在是太难了,决定找工作。但是他想去的一些公司,对数学要求非常高,经常会在笔试和面试中出一些数学题为难人,为了拿到一个满意的offer,他决定刻苦研究数学题,并利用编程来验证相关计算是否正确,这次,他又遇到了一个头疼的数学题,想找你寻求帮助。题目是这样的:给定一个正整数,你需要找到一种由若干个不相等的 3 3 3 的幂次方的和或差表示的方式,并按照每一项从大到小的顺序输出。
输入描述
一个正整数
x
x
x 。
1
≤
x
≤
1
0
9
1\le x\le 10^9
1≤x≤109
输出描述
一个表达式,最终的答案必须等于 x x x 。表达式的每一项必须是 3 3 3 的幂,且不能有两项相同。
例如, 18 18 18 必须输出为 27 − 9 27-9 27−9 而不能是 9 + 9 9+9 9+9 。
样例1
输入
30
输出
27+3
样例2
输入
300
输出
243+81-27+3
题解:三进制+递归拆解
对于一个数字 x x x,它一定可以表示为若干个2的整数幂的和,比如 7 = 2 2 + 2 1 + 2 0 7=2^2+2^1+2^0 7=22+21+20,但是,它不一定能表示为若干个3的整数幂的和,比
如 30 = 3 3 + 3 1 30=3^3+3^1 30=33+31,这个是可以表示成若干个3的整数幂的和的,但是对于26这个数字来说,它不能被表示为若干个3的整数幂的和,但是
可以被表示为
26
=
3
3
−
3
0
26=3^3-3^0
26=33−30
那么我们分析一下:什么样的数字可以被表示为若干个3的整数幂的和,我们设
i
i
i为3的整数幂的最高次幂,那么它可以表示的最大数字不
超过 3 0 + 3 1 + . . . + 3 i = 3 i + 1 − 1 2 3^0+3^1+...+3^i=\frac{3^{i+1}-1}{2} 30+31+...+3i=23i+1−1
因此,对于在该范围内的数字,是可以用若干个3的整数幂的和表示的,我们取 i = l o g 3 ( x ) i=log_3(x) i=log3(x),那如果有 x ≥ 3 i + 1 − 1 2 x\ge \frac{3^{i+1}-1}{2} x≥23i+1−1,则我们需要向
i + 1 i+1 i+1借一位,然后让 x = x − 3 i + 1 x=x-3^{i+1} x=x−3i+1,然后再递归处理 x x x,注意递归处理时,需要对 x x x取绝对值,然后后面所得到的幂次也需要变换符
号,比如加号变成减号,或者减号变成加号。
从递归函数的定义出发,来分析这道题
- 递归函数停止调用的条件: x ≤ 1 x\le 1 x≤1,递归结束。
- 递归函数拆解,分治计算的递推式
取 i = l o g 3 ( x ) i=log_3(x) i=log3(x), t o t a l = 3 i + 1 − 1 2 total=\frac{3^{i+1}-1}{2} total=23i+1−1
- 情况1: x ≤ t o t a l x\le total x≤total,则说明可以由 3 0 , 3 1 , . . . 3 i 3^0,3^1,...3^i 30,31,...3i的和来组成,根据题目从大到小排序的要求,则将 x x x的值减去 3 i 3^i 3i,将 3 i 3^i 3i添加进答案数组中,然后继续递归 x x x
- 情况2: x > t o t a l x>total x>total,则说明不可以由 3 0 , 3 1 , . . . 3 i 3^0,3^1,...3^i 30,31,...3i的和来组成,则需要向 3 i + 1 3^{i+1} 3i+1借位,然后需要变换符号位,并最终令 x = 3 i + 1 − x x=3^{i+1}-x x=3i+1−x,将 3 i + 1 3^{i+1} 3i+1添加进答案数组中,然后继续递归 x x x
C++
#include<bits/stdc++.h>
using namespace std;
int x,flag; //flag用来判断当前的3次幂是正是负
vector<int>res; //存储答案数组
int pow(int a,int p){ //计算a^p
int ans=1;
for(int i=1;i<=p;i++){
ans*=a;
}
return ans;
}
void dfs(int x){
if(x<=1){ //递归函数结束条件
if(x==1)res.push_back(x*flag);
return;
}
int p=log(x)/log(3);
int total=(pow(3,p+1)-1)/2;
if(total>=x){ //可以由若干个3的幂次构成,直接递归计算即可
int val=pow(3,p);
res.push_back(val*flag); //记录答案
dfs(x-val); //继续递归计算
}
else{ //不能由1 3 ... 3^p 表示 需要向上借位
int val=pow(3,p+1);
res.push_back(val*flag);
flag*=-1; //借位,因此需要变换符号位
x=val-x; //继续计算剩下的x
dfs(x);
}
}
int main(){
cin>>x;
flag=1;
dfs(x);
int n=res.size();
for(int i=0;i<n;i++){
if(i==0){
cout<<res[i]; //第一个数字,不需要由加号
}
else{
if(res[i]>0)cout<<"+";
cout<<res[i];
}
}
return 0;
}
Java
import java.util.*;
public class Main {
static int x, flag; // flag用来判断当前的3次幂是正是负
static List<Integer> res = new ArrayList<>(); // 存储答案数组
static int pow(int a, int p) { // 计算a^p
int ans = 1;
for (int i = 1; i <= p; i++) {
ans *= a;
}
return ans;
}
static void dfs(int x) {
if (x <= 1) { // 递归函数结束条件
if (x == 1) res.add(x * flag);
return;
}
int p = (int) (Math.log(x) / Math.log(3));
int total = (pow(3, p + 1) - 1) / 2;
if (total >= x) { // 可以由若干个3的幂次构成,直接递归计算即可
int val = pow(3, p);
res.add(val * flag); // 记录答案
dfs(x - val); // 继续递归计算
} else { // 不能由1 3 ... 3^p 表示 需要向上借位
int val = pow(3, p + 1);
res.add(val * flag);
flag *= -1; // 借位,因此需要变换符号位
x = val - x; // 继续计算剩下的x
dfs(x);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
x = scanner.nextInt();
flag = 1;
dfs(x);
int n = res.size();
for (int i = 0; i < n; i++) {
if (i == 0) {
System.out.print(res.get(i)); // 第一个数字,不需要由加号
} else {
if (res.get(i) > 0) System.out.print("+");
System.out.print(res.get(i));
}
}
}
}
Python3
import math
x = int(input())
flag = 1 # flag用来判断当前的3次幂是正是负
res = [] # 存储答案数组
def pow(a, p): # 计算a^p
return a ** p
def dfs(x):
global flag # 将flag声明为全局变量
if x <= 1: # 递归函数结束条件
if x == 1:
res.append(x * flag)
return
p = int(math.log(x) / math.log(3))
total = (pow(3, p + 1) - 1) / 2
if total >= x: # 可以由若干个3的幂次构成,直接递归计算即可
val = pow(3, p)
res.append(val * flag) # 记录答案
dfs(x - val) # 继续递归计算
else: # 不能由1 3 ... 3^p 表示 需要向上借位
val = pow(3, p + 1)
res.append(val * flag)
flag *= -1 # 借位,因此需要变换符号位
x = val - x # 继续计算剩下的x
dfs(x)
dfs(x)
n = len(res)
for i in range(n):
if i == 0:
print(res[i], end='') # 第一个数字,不需要由加号
else:
if res[i] > 0:
print("+", end='')
print(res[i], end='')
第二题:a的组合方式
在线测评链接:http://121.196.235.151/p/P1080
题目描述
在一首音乐作品中,有一段旋律由 n n n 个音符组成,每个音符的音高由一个正整数表示。作曲家希望为这段旋律添加一些和声,以产生更加丰富的音乐效果。为了实现这个目标,他们想要找到一组和弦,使得这组和弦的构成方式与旋律的音符间隔完全匹配。
为了帮助作曲家实现这个目标,你需要编写一个程序来计算出可能的音符数组 a a a 的组合方式数目。给定一个长度为 n − 1 n-1 n−1 的和弦数组 b b b,你的任务是计算出可能的音符数组 a a a 的组合方式数目。其中,和弦数组 b b b 中的每个元素 b [ i ] = a [ i ] + a [ i + 1 ] b[i] = a[i]+ a[i+1] b[i]=a[i]+a[i+1] 。
作曲家们知道,在音乐中,和弦的构成方式是非常丰富多样的。因此,他们希望通过这个程序来快速地计算出所有可能的音符数组 a a a 的组合方式数目,以便在创作过程中进行参考。
输入描述
第一行输出为一个整数 n ( 1 ≤ n ≤ 1 e 5 ) n(1 \leq n \leq 1e5) n(1≤n≤1e5) 。
第二行输出为 n − 1 n-1 n−1 个整数,第 i i i 个整数为 b i ( 1 ≤ b i ≤ 1 e 9 ) b_i(1 \leq b_i \leq 1e9) bi(1≤bi≤1e9) 。
输出描述
输出为一个整数,表示数组 a a a 有多少种可能。
样例1
输入
3
2 2
输出
1
样例解释
b 1 = a 1 + a 2 = 2 b_1=a_1+a_2=2 b1=a1+a2=2
b 2 = a 2 + a 3 = 2 b_2=a_2+a_3=2 b2=a2+a3=2
由于 a i a_i ai均为正整数,因此只有一个方案 [ 1 , 1 , 1 ] [1,1,1] [1,1,1]
样例2
输入
3
2 1
输出
0
样例解释
b 2 = a 2 + a 3 = 1 b_2=a_2+a_3=1 b2=a2+a3=1,由于 a i a_i ai均为正整数,因此没有任何方案可以满足条件。
样例3
输入
4
12 34 236
输出
11
题解:思维题
b 1 = a 1 + a 2 b_1=a_1+a_2 b1=a1+a2
b 2 = a 2 + a 3 b_2=a_2+a_3 b2=a2+a3
b 3 = a 3 + a 4 b_3=a_3+a_4 b3=a3+a4
…
可以得到:
a 2 = b 1 − a 1 a_2=b_1-a_1 a2=b1−a1
a 3 = b 2 − b 1 + a 1 a_3=b_2-b_1+a_1 a3=b2−b1+a1
…
已知所有的 b i b_i bi,因此所有 a i a_i ai的组合方式数目其实就是取决于 a 1 a_1 a1可以取的值的个数
根据所有的 a i > 0 a_i>0 ai>0可以不断地去计算 a 1 a_1 a1取值的上界和下界,然后一边读取 b i b_i bi,一边更新上下界即可
a 1 < b 1 a_1<b_1 a1<b1
a 1 > b 1 − b 2 a_1> b_1-b_2 a1>b1−b2
a 1 < b 1 − b 2 + b 3 a_1<b_1-b_2+b_3 a1<b1−b2+b3
a 1 > b 1 − b 2 + b 3 − b 4 a_1>b_1-b_2+b_3-b_4 a1>b1−b2+b3−b4
…
C++
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n; // 读取输入的整数
int b[n+1];
for(int i = 1; i <= n; i++) {
cin >> b[i]; // 读取输入的数组
}
int lower = 0, upper = 1e9; // a1的下界和上界(开区间)
int total = 0;
for(int i = 1; i < n; i++) {
if(i % 2 == 1) { // i为奇数
total += b[i]; // 更新上界值
upper = min(upper, total); // 更新a1上界
} else {
total -= b[i]; // 更新下界值
lower = max(lower, total); // 更新a1下界
}
}
int ans = max(0, upper - lower - 1); // lower<a1<upper 因此方案数是upper-lower-1
cout << ans;
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(); // 读取输入的整数
int[] b = new int[n+1];
for(int i = 1; i <= n; i++) {
b[i] = scanner.nextInt(); // 读取输入的数组
}
int lower = 0, upper = (int)1e9; // a1的下界和上界(开区间)
int total = 0;
for(int i = 1; i < n; i++) {
if(i % 2 == 1) { // i为奇数
total += b[i]; // 更新上界值
upper = Math.min(upper, total); // 更新a1上界
} else {
total -= b[i]; // 更新下界值
lower = Math.max(lower, total); // 更新a1下界
}
}
int ans = Math.max(0, upper - lower - 1); // lower<a1<upper 因此方案数是upper-lower-1
System.out.println(ans);
}
}
Python
n=int(input())
b=[0]+list(map(int,input().split()))
lower,upper=0,10**9 #a1的下界和上界(开区间)
total=0
for i in range(1,n):
if i%2==1: #i为奇数
total+=b[i] #更新上界值
upper=min(upper,total) #更新a1上界
else:
total-=b[i] #更新下界值
lower=max(lower,total) #更新a1下界
ans=max(0,upper-lower-1) # lower<a1<upper 因此方案数是upper-lower-1
print(ans)
第三题 奇妙糖果
在线测评链接:http://121.196.235.151/p/P1081
题目描述
在一个神奇的糖果工厂中,有一种特殊的糖果叫做“奇妙糖果”。这种糖果非常受欢迎,因为它的口感和味道都非常好。
奇妙糖果的制作非常复杂,制造过程中要求每种原料的出现次数必须是
k
k
k 的倍数,其中
k
k
k 是一个给定的正整数。
在制造奇妙糖果的过程中,制造商们发现,他们可以通过选择不同的原料配方来制造出不同的口感和味道的糖果。他们想要快速计算出可以制造的所有糖果的数量,以便在生产计划中进行参考。
为了实现这个目标,他们需要编写一个程序来计算出可以制造的所有奇妙糖果的数量。
给定
n
n
n 个原料和正整数
k
k
k,以及每个原料的种类
a
i
a_i
ai ,你的任务是计算出可以制造的所有的奇妙糖果的数量。
注意,这里的“奇妙糖果”是指使用
n
n
n 个原料的任意非空子序列制造出的糖果,其中每种原料的出现次数都是
k
k
k 的倍数。
子序列定义是数组中选择若干个元素按照原顺序组成的新数组。
输入描述
第一行输出为两个整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105) 和 k ( 1 ≤ k ≤ 50 ) k(1 \leq k \le 50) k(1≤k≤50) ,表示原料的个数。
第二行输出为 n n n 个整数,第 i i i 个整数为 a i a_i ai ( 1 ≤ a i ≤ 50 1\le a_i \le 50 1≤ai≤50),表示该原料的种类的编号。
输出描述
输出为一个整数,表示有多少使用 n n n个原料的任意非空子序列制造出的糖果,其中每种原料的出现次数都是 k k k的倍数。
样例1
输入
4 2
1 2 1 2
输出
3
说明
三种方案分别为:{1,1}, {2,2}, {1,2,1,2}
样例2
输入
5 2
1 2 1 2 1
输出
7
说明
七种方案分别为:{1,0,1,0,0}, {1,0,0,0,1}, {0,0,1,0,1}, {0,2,0,2,0}, {1,2,1,2,0}, {0,2,1,2,1}, {1,2,0,2,1} (0代表不选对应位置的原料)
题解:组合数学计数+乘法原理
每一种原料出现次数都必须要是
k
k
k的整数倍,因此其出现的次数就一定是
0
,
k
,
2
k
,
.
.
.
,
m
k
0,k,2k,...,mk
0,k,2k,...,mk
设第
i
i
i种原料的总个数为
a
i
a_i
ai,我们可以使用组合计数去枚举选择的方案数
c
i
=
C
(
a
i
,
0
)
+
C
(
a
i
,
k
)
+
C
(
a
i
,
2
k
)
+
.
.
.
c_i=C(a_i,0)+C(a_i,k)+C(a_i,2k)+...
ci=C(ai,0)+C(ai,k)+C(ai,2k)+...
然后根据乘法原理可知,总的选择方案数就一定为所有原料选择的方案数累乘
即
(
∏
i
=
1
50
c
i
)
−
1
(\prod_{i=1}^{50}c_i)-1
(∏i=150ci)−1
减1,是因为不能一种原料都不选,因为题目要求是非空子序列,因此需要把总的计算结果减去1。
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10,mod=1E9+7;
long long n,w[N],k;
unordered_map<int,int>cnts; //统计每一种糖果出现的次数
long long fac[N], ifac[N]; //阶乘和阶乘的逆元
long long qmi(long long a, long long b,int MOD) { //快速幂
long long res = 1;
while (b > 0) {
if (b & 1) {
res = res * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return res;
}
long long Comb(int a, int b,int mod) { //求C(a,b)的组合数
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
int main()
{
cin>>n>>k;
fac[0] = ifac[0] = 1; //阶乘和阶乘对应的逆元初始化
for (int i = 1; i <=n; i++) { //预处理阶乘和阶乘的逆元
fac[i] = fac[i - 1] * i % mod;
ifac[i]=ifac[i-1]*qmi(i,mod-2,mod)%mod;
}
for(int i=0;i<n;i++)
{
int x;
cin>>x;
cnts[x]++;
}
long long res=1;
for(auto &candy:cnts) //枚举第i种糖果选0个 k个 2k个 ...
{
int v=candy.second;
if(v<k)continue;
long long s=0;
for(int i=0;i<=v;i+=k){
s=(s+Comb(v,i,mod))%mod; //计算组合数
}
res=(res*s)%mod;
}
res=(res-1); //去除空集的情况
cout<<res<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
static final int N = (int)1E5+10, MOD = (int)1E9+7;
static long[] fac = new long[N], ifac = new long[N]; // 阶乘和阶乘的逆元
static Map<Integer, Integer> cnts = new HashMap<>(); // 统计每一种糖果出现的次数
// 快速幂
static long qmi(long a, long b) {
long res = 1;
while (b > 0) {
if ((b & 1) == 1) {
res = res * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return res;
}
// 求C(a,b)的组合数
static long Comb(int a, int b) {
return fac[a] * ifac[b] % MOD * ifac[a - b] % MOD;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(), k = scanner.nextInt();
fac[0] = ifac[0] = 1; // 阶乘和阶乘对应的逆元初始化
for (int i = 1; i <= n; i++) { // 预处理阶乘和阶乘的逆元
fac[i] = fac[i - 1] * i % MOD;
ifac[i] = ifac[i - 1] * qmi(i, MOD - 2) % MOD;
}
for (int i = 0; i < n; i++) {
int x = scanner.nextInt();
cnts.put(x, cnts.getOrDefault(x, 0) + 1);
}
long res = 1;
for (Map.Entry<Integer, Integer> candy : cnts.entrySet()) { // 枚举第i种糖果选0个 k个 2k个 ...
int v = candy.getValue();
if (v < k) continue;
long s = 0;
for (int i = 0; i <= v; i += k) {
s = (s + Comb(v, i)) % MOD; // 计算组合数
}
res = (res * s) % MOD;
}
res = (res - 1); // 去除空集的情况
System.out.println(res);
}
}
Python
MOD = 10**9 + 7
N = 10**5 + 10
fac = [0]*N
ifac = [0]*N
cnts = {} # 统计每一种糖果出现的次数
# 快速幂
def qmi(a, b):
res = 1
while b > 0:
if b & 1:
res = res * a % MOD
a = a * a % MOD
b >>= 1
return res
# 求C(a,b)的组合数
def Comb(a, b):
return fac[a] * ifac[b] % MOD * ifac[a - b] % MOD
n, k = map(int, input().split())
fac[0] = ifac[0] = 1 # 阶乘和阶乘对应的逆元初始化
for i in range(1, n+1): # 预处理阶乘和阶乘的逆元
fac[i] = fac[i - 1] * i % MOD
ifac[i] = ifac[i - 1] * qmi(i, MOD - 2) % MOD
w=list(map(int,input().split()))
for x in w:
if x in cnts:
cnts[x] += 1
else:
cnts[x] = 1
res = 1
for v in cnts.values(): # 枚举第i种糖果选0个 k个 2k个 ...
if v < k: continue
s = 0
for i in range(0, v+1, k):
s = (s + Comb(v, i)) % MOD # 计算组合数
res = (res * s) % MOD
res = (res - 1) # 去除空集的情况
print(res)