leetcode 第 469 场算法比赛

作者: | 更新日期:

矩阵快速幂

本文首发于公众号:天空的代码世界,微信号:tiankonguse

零、背景

这次比赛是国庆调休日,所以没参加比赛。
晚上找时间做了一下比赛,发现比较简单,第四题是矩阵快速幂,模板题。

A: 循环
B: 前缀和+枚举或枚举
C: 状态DP
D: 状态DP+矩阵快速幂加速

代码地址: https://github.com/tiankonguse/leetcode-solutions

一、计算十进制表示

题意:给一个十进制数字,求各位非0数字,从大到小排列。

思路:循环计算每一位数字即可

二、分割数组得到最小绝对差

题意:给一个数组,问是否可以恰好拆分为两个子数组,左边的严格递增,右边的严格递减。如果可以,如何拆分才能使两个子数组和的绝对值差值最小。

思路:贪心或枚举

可以证明,如果存在答案,则数组必然是先严格升序在严格降序。
对于中间的最大值,可以归属于左边,也可以归属于右边,所以必然存在两个拆分。

故可以贪心先找到最大值,然后判断两边是否满足升序和降序,不满足则没有答案。
如果满足条件,分别求和并计算差值即可。

当然,没发现上面的规律也没关系。
可以预处理出前缀和,以及所有前缀是否满足严格升序与所有后缀是否满足严格降序。
然后枚举所有拆分,并判断是否满足严格升序与降序,满足了,通过前缀和计算出子数组的区间和,最后求差值。

三、锯齿形数组的总数 I

题意:需要在区间 [l,r] 依次选择 n 个数字(可以重复),要求相邻数字不同,连续三个数字不严格递增或递减。
问有多少种方案满足条件。

思路:状态DP

题目的条件是相邻三个元素的限制,故需要定义前两个元素关系的状态,从而可以找到满足条件的第三个元素。

状态定义:f(n, v, dir) 第 n 个数字值为 v 且 前一个元素与当前元素的关系为 dir 时的方案数。
dir 分为上升和下降,可以使用 UP=0DOWN=1 表示。

状态转移:

f(n, v, UP) = sum(f(n-1, i, DOWN)), i < v;
f(n, v, DOWN) = sum(f(n-1, i,UP)),  i > v

方程解释:当前状态为 UP 时,来源的数字值必须小于当前值 v,且来源状态必须是 DOWN。
当前状态为 DOWN 时,来源的数字值则必须大于当前值 v,且来源状态必须是 UP。

n--;
while (n--) {
  fill(dp[UP].begin(), dp[UP].end(), 0);
  fill(dp[DOWN].begin(), dp[DOWN].end(), 0);
  // UP -> DOWN
  for (int i = 1; i <= R; i++) {
    dp[DOWN][i] += sum[UP][i - 1];
  }
  // DOWN -> UP
  for (int i = R - 1; i >= 0; i--) {
    dp[UP][i] += sum[DOWN][i + 1];
  }
  CalSUm();
}

由于使用到了小于某个值的方案和,所以每轮还需要处理出前缀和。

auto CalSUm = [&]() {
  fill(sum[UP].begin(), sum[UP].end(), 0);
  fill(sum[DOWN].begin(), sum[DOWN].end(), 0);
  sum[UP][0] = dp[UP][0] % mod;
  for (int i = 1; i <= R; i++) {
    sum[UP][i] = (sum[UP][i - 1] + dp[UP][i]) % mod;
  }
  sum[DOWN][R] = dp[DOWN][R] % mod;
  for (int i = R - 1; i >= 0; i--) {
    sum[DOWN][i] = (sum[DOWN][i + 1] + dp[DOWN][i]) % mod;
  }
};

对于初始化,所有数字值的所有方向默认都是1个,此时 n=1 需要特殊判断。

int R = r - l;
if (n == 1) {
  return R + 1;
}
vector<vector<ll>> dp(2, vector<ll>(R + 1, 0));
for (int i = 0; i <= R; i++) {
  dp[UP][i] = 1;
  dp[DOWN][i] = 1;
}

复杂度:O(N*(R-L+1))

四、锯齿形数组的总数 II

题意:与第三题一样,n 数据范围变为 10^9,值域缩小为 75

思路:矩阵幂加速

观察第三题的状态转移方程,UP 状态来源于 DOWN 的一些状态的和, DOWN 的状态同样来源于 UP 的状态和,这些关系可以构造为矩阵后,通过矩阵乘来代替上面的循环即可。

状态矩阵大小定义为 2R,例如上面一部分是 UP 的状态,下面一部分是 DOWN 的状态。

int R = r - l + 1;
int R2 = R * 2;
Matrix state(R2);
for (int i = 0; i < R; i++) {
  for (int j = 0; j < R; j++) {  // DOWN -> UP
    if (j > i) {
      state.a[i][R + j] = 1;
    }
  }
  for (int j = 0; j < R; j++) {
    if (j < i) {
      state.a[R + i][j] = 1;
    }
  }
}

由于矩阵满足结合律,因此可以使用快速幂来加速运算。

Matrix stateN = state.pow(n - 1);
ll ans = 0;
for (int i = 0; i < R2; i++) {
  for (int j = 0; j < R2; j++) {
    ans = (ans + stateN.a[i][j]) % mod;
  }
}
return ans;

复杂度:R^3 * log(n)

五、最后

这次比赛整体比较简单,前三题都属于基础知识,第四题如果之前没见过矩阵幂相关内容,那就做不出这道题了。

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号: tiankonguse
公众号 ID: tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越