A. 位数求和
题目描述
牛牛想知道所有的长度为 n 的数中,各个位上的数字之和为 m 的这些数的和是多少呢。给定 n 和 m,求这些数的和。
备注:
1 ≤ n ≤ 6,1 ≤ m ≤ 9 ∗ n
示例1
输入
2, 3
输出
63
说明
12 + 21 + 30 = 63
解法一:暴力枚举
思路分析
由于范围很小,所以可以暴力枚举每一个长度为 n 的数,看其是否符合条件。
时间复杂度:$O(10^n)$。总共枚举 $10^n - 10^{n-1}$ 个数字。
空间复杂度:$O(1)$。
代码实现
public long sum (int n, int m) {
long res = 0;
int max = (int)Math.pow(10, n);
for(int i = (int)Math.pow(10, n - 1); i < max; i++){
int j = i, sum = 0;
while(j > 0) {sum += j % 10; j /= 10;}
if(sum == m) res += i;
}
return res;
}
解法二:DFS
思路分析
从前到后依次枚举每一位上的数字,到第 $n$ 位时停止,若此时的数满足条件,则计入总和中。
可以加入剪枝操作,记前面的位上数字之和为 a,若 $a + b = m (b \in [1,9])$ 则只需枚举 $[1, b]$ 即可。
时间复杂度:$O(10^n)$。递归 $n$ 层,每层循环 10 次。但加入了剪枝策略,因此时间开销比解法一要小一些。
空间复杂度:$O(n)$。需要递归 $n$ 层。
代码实现
long res = 0;
public long sum(int n, int m) {
for (int i = 1; i <= 9 && i <= m; i++) dfs(n - 1, m - i, i);
return res;
}
public void dfs(int n, int m, int num) {
if(n == 0 && m == 0) res += num;
if(n == 0) return;
for (int i = 0; i <= 9 && i <= m; i++) dfs(n - 1, m - i, num * 10 + i);
}
B. 不可思议
题目描述
给定一颗节点编号为 1 ~ n 的,且以 1 为根的树,给出 n 组询问,每次询问给定一个数对 (x, y),求 i 为 x 到根路径上的点(包括 x 和根) $∑_i(y+2i)xor(y+i)$.
对于这 n 组询问的答案,不需要依次输出 n 个数,你只需要输出他们的和对 998244353 的取模即可。
树的信息以及询问不会直接给出,输入数据只包含随机种子,具体生成方式请仔细阅读备注内容。
备注:
输入数据包含5个整数,n,seed1,seed2,seed3,x.
树的信息生成伪代码如下
//////////////////////1/////////////////////
定义边集数组u[],v[] //u[i],v[i]表示第i条边的两个端点
定义变量seed4
定义循环变量i从1到n-1
循环体开始
seed4=(seed1+seed2)%998244353*seed3%998244353;
u[i]=i+1;
v[i]=(seed4%i)+1;
seed3=seed2;
seed2=seed1;
seed1=seed4;
循环体结束
//////////////////////1/////////////////////
询问信息生成伪代码如下,顺带一提,第一次询问的x会在输入中给出
//////////////////////2/////////////////////
定义变量lastans,初始值为0
定义变量ret,初始值为0
定义变量y,初始值为0 //含义见题干
定义函数ans(x,y),返回值为对数对(x,y)这组询问的答案 //这里“询问”的含义见题干
定义变量z
定义循环变量i从1到n
循环体开始
z=ans(x,y);
ret=(ret+z)%998244353;
lastans=z;
x=((x+lastans)^ret)%n+1;
y=lastans;
循环体结束
//////////////////////2/////////////////////
输出一个整数,表示答案。
n<=10^5,seed1,seed2,seed3<=10^9,x<=n
保证构造出的数据合法。
示例1
输入
3,1,1,1,1
输出
7
示例2
输入
4,2,2,3,1
输出
119
解法:直观解法
思路分析
这题和它的名字一样,题目是 "不可思议" 的长,乍看会以为很难。看完题目后发现,只需要实现函数 ans 即可,“不可思议”的简单……
就是从 x 不断的往上找父亲节点 i 直到根节点,返回路径上 $(y+2*i) xor (y+i)$ 的总和。
可以用数学归纳法证明 $u[i]$ 的父节点是 $v[i]$。这个证明比较简单,就不多赘述了。
时间复杂度:$O(n*k)$。$k$ 是树的深度,总共 $n$ 组询问,每次询问最多循环计算 $k$ 次。
空间复杂度:$O(n)$。需要存储 $n - 1$ 个节点的父节点。
代码实现
int[] p; // p[i] 是 i 的父节点
public long work(int n, long seed1, long seed2, long seed3, int x) {
// 生成树
p = new int[n + 1];
for (int i = 1; i < n; i++) {
long seed4 = (seed1 + seed2) % 998244353 * seed3 % 998244353;
p[i + 1] = (int) (seed4 % i) + 1;
seed3 = seed2;
seed2 = seed1;
seed1 = seed4;
}
// 询问信息生成
long lastans = 0, ret = 0, y = 0;
for (int i = 1; i <= n; i++) {
long z = ans(x, y);
ret = (ret + z) % 998244353;
lastans = z;
x = (int) ((x + lastans) ^ ret) % n + 1;
y = lastans;
}
return ret;
}
public long ans(int x, long y) {
long ans = 0;
while (x != 1) {
ans += (y + 2 * x) ^ (y + x);
x = p[x];
}
ans += (y + 2) ^ (y + 1);
return ans;
}
C. 牛牛晾衣服
题目描述
牛牛有 n 件带水的衣服,干燥衣服有两种方式。
一、是用烘干机,可以每分钟烤干衣服的 k 滴水。
二、是自然烘干,每分钟衣服会自然烘干 1 滴水。
烘干机比较小,每次只能放进一件衣服。
注意,使用烘干机的时候,其他衣服仍然可以保持自然烘干状态,现在牛牛想知道最少要多少时间可以把衣服全烘干。
备注:
第一个参数n(1 ≤ n ≤ 105),代表一共有多少件衣服。
第二个参数为n个数(1 ≤ an ≤ 109)组成的数组,代表n件衣服分别有多少水滴水。
第三个参数k(1 ≤ k ≤ 109),代表烘干机每分钟能烘干k滴水。
程序应返回:一个整数,代表使n件衣服全部干燥所需要的最少的时间。
示例1
输入
3,[2,3,9],5
输出
3
说明
前两分钟对第三件衣服进行烘干机烘干,使得衣服的水份分别为0,1,0,所以最快三分钟可以烘干。
解法:二分法
思路分析
先不考虑怎么用程序实现,如果让你自己算最少烘干时间,你会怎么算?
是不是对直接算烘干时间毫无头绪?那如果给你个时间 t,让你判断 t 分钟后衣服是否全部烘干。这个问题是不是就简单很多了?
因此我们可以暴力遍历每一个时刻,看是否能烘干,最小的时刻就是题目要求的答案了。
但是暴力匹配耗时太多了啊,怎么办呢?
我们发现这是在线性连续区域内进行遍历的,显然使用二分法可以大大降低时间复杂度。
时间复杂度:$O(nlogn)$。二分搜索的复杂度是 $O(logn)$, 每次搜索时判断能否全部烘干需要 $O(n)$。
空间复杂度:$O(1)$。
代码实现
public int solve (int n, int[] a, int k) {
Arrays.sort(a);
int l = a[0], r= a[a.length - 1];
while(l < r){
int mid = (l + r) >> 1;
if(check(mid, k, a)) r = mid;
else l = mid + 1;
}
return r;
}
public boolean check(int t, int k, int[] a){
int cnt = 0; //记录烘干机用时
for(int i = a.length - 1; i >= 0 && cnt <= t; i--){
if(a[i] <= t) break;
cnt += (a[i] - t) / (k - 1);
if(((a[i] - t) % (k - 1)) != 0) cnt++;
}
return cnt <= t;
}
写在最后
大家好,我是往西汪,一位坚持原创的新人博主。
如果本文对你有帮助,请动动小手指点个赞。你的支持是我创作路上的最大动力。谢谢大家!
也欢迎来公众号【往西汪】找我玩耍~