算法题解 - 牛客编程巅峰赛S1第3场 - 青铜&白银组

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;
}

写在最后

大家好,我是往西汪,一位坚持原创的新人博主。
如果本文对你有帮助,请动动小手指点个赞。你的支持是我创作路上的最大动力。谢谢大家!
也欢迎来公众号【往西汪】找我玩耍~

你可能感兴趣的:(java,算法-数据结构,dfs,二分查找)