一、3512. 使数组和能被 K 整除的最少操作次数
题目链接
本题实际上求的就是数组 nums
和的余数,代码如下:
class Solution {
public int minOperations(int[] nums, int k) {
int s = 0;
for(int x : nums){
s += x;
}
return s % k;
}
}
二、3513. 不同 XOR 三元组的数目 I
题目链接
本题给出的数组 nums
包含 [1,n]
中的所有数,问选 3 个时可以得到的所有异或值,分情况讨论:
n = 1
,只能与自己异或,答案为{1}
n = 2
,必有两个相同值异或(a^a=0),然后再与 1/2 异或,答案为{1,2}
n > 2:
1 ^ 2 ^ 3 = 0
,所以可以取到0
a ^ a ^ a = a
,所以可以得到[1,n]
- 对于大于 n 的值,可以使用构造的方式来获得,比如说
1101
,可以通过{1000,100,1}
获得;1100
可以通过{1000,101,1}
获得;得到一般公式,对于 [n+1, 2 l 2^{l} 2l-1] 中任意一个数 a 来说,它可以通过 { 2 l − 1 2^{l-1} 2l−1,a ^ 1 ^ 2 l − 1 2^{l-1} 2l−1,1} 获得。 - 特殊情况,a = 2 l − 1 2^{l-1} 2l−1 + 1(即
1001
,100001
这种情况),无法使用上述公式得到,这里特殊处理,可以使用 { 2 l − 1 2^{l-1} 2l−1,2,3} 得到
代码如下:
class Solution {
public int uniqueXorTriplets(int[] nums) {
int n = nums.length;
if(n == 1) return 1;
if(n == 2) return 2;
// [1, n] 选 3 个数
for(int i = 31; i >= 0; i--){
if((n >> i & 1) == 1){
return 1 << (i + 1);
}
}
return -1;
}
}
三、3514. 不同 XOR 三元组的数目 II
题目链接
本题数据范围小,可以使用 O( n 2 n^{2} n2) 的时间复杂度解决,先处理出 nums数组
中两个数的异或值,然后拿该异或值再与 nums
数组异或。代码如下:
class Solution {
public int uniqueXorTriplets(int[] nums) {
int mx = 0;
for(int x : nums){
mx = Math.max(mx, x);
}
int u = 1 << (32 - Integer.numberOfLeadingZeros(mx));
// 这里不要使用 set 来存储,会超时
boolean[] has = new boolean[u];
for(int i = 0; i < nums.length; i++){
for(int j = i; j < nums.length; j++){
has[nums[i] ^ nums[j]] = true;
}
}
boolean[] has1 = new boolean[u];
for(int i = 0; i < u; i++){
if(!has[i]) continue;
for(int x : nums){
has1[i ^ x] = true;
}
}
int ans = 0;
for(boolean x : has1){
if(x) ans++;
}
return ans;
}
}
四、3515. 带权树中的最短路径
题目链接
本题的难点在于怎么知道更新的(u,v)
之间的边权会影响根节点到哪些点的路径距离,由于本题是一个树,所以影响的肯定是(u,v)
的所有子节点,那么问题变成了如何维护一个节点的所有子节点?这里可以使用 dfs时间戳
来维护:
- dfs时间戳就是通过先序遍历,对于每个节点
x
,记录进入x
节点的时间in[x]
以及出该节点的时间out[x]
,如果其他节点的[in[y], out[y]]
在[in[x],out[x]]
中,说明 y 是 x 的子节点。
此时,对于 (u,v)
之间边权的更新,就可以转换成 [in[v], out[v]]
区间的更新(u 是 v 的父节点),可以使用差分的思想来做,由于数据最多只能支持 O(nlogn) 的时间复杂度,所以需要一个 logn 查询/修改的数据结构来维护,可以选择线段树/树状数组,这里使用树状数组。
代码如下:
class Solution {
// 树状数组模板
static class FenwickTree{
int[] tree;
public FenwickTree(int n){
tree = new int[n + 1];
}
public void update(int i, int val){
while(i < tree.length){
tree[i] += val;
i += i & -i;
}
}
public int pre(int i){
int res = 0;
while(i > 0){
res += tree[i];
i -= i & -i;
}
return res;
}
}
// 根据进出时间来记录每次修改干涉的路径
// 根据{进出时间+差分}来修改/得到最短路径
public int[] treeQueries(int n, int[][] edges, int[][] queries) {
List<Integer>[] g = new ArrayList[n+1];
Arrays.setAll(g, e->new ArrayList<>());
for(int[] e : edges){
int u = e[0];
int v = e[1];
g[u].add(v);
g[v].add(u);
}
in = new int[n+1];
out = new int[n+1];
dfs(1, -1, g);
weight = new int[n+1];// 记录边权
FenwickTree t = new FenwickTree(n);
for(int[] e : edges){
update(e[0], e[1], e[2], t);
}
List<Integer> ans = new ArrayList<>();
for(int[] q : queries){
if(q[0] == 1){
update(q[1], q[2], q[3], t);
}else{
ans.add(t.pre(in[q[1]]));
}
}
return ans.stream().mapToInt(i -> i).toArray();
}
int clock = 0;
int[] in;
int[] out;
int[] weight;
void dfs(int x, int fa, List<Integer>[] g){
in[x] = ++clock;
for(int y : g[x]){
if(y == fa) continue;
dfs(y, x, g);
}
out[x] = clock;
}
void update(int x, int y, int w, FenwickTree t){
if(in[x] > in[y]){
y = x;
}
int d = w - weight[y];
weight[y] = w;
t.update(in[y], d);
t.update(out[y]+1, -d);
}
}