CodeForces Round 881 div3
A题
题意:数组arr,任选两个元素出来,和为相减的绝对值,求选完所有元素之后的和的最大值。
private static void problemA(Fast fast) {
int t = fast.nextInt();
while(--t >= 0) {
int n = fast.nextInt();
var arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = fast.nextInt();
}
Arrays.sort(arr);
int l = 0, r = n - 1;
int sum = 0;
while(l <= r) {
sum += (arr[r--] - arr[l++]);
}
fast.write(sum + "\n");
}
fast.flush();
}
B题
题意:挑出所有连续的非正数连续子数组,答案就是子数组数量。特例,子数值初始元素值不能为0;
private static void problemB(Fast fast) {
int t = fast.nextInt();
while(--t >= 0) {
int n = fast.nextInt();
long sum = 0;
var arr = new int[n + 1];
arr[0] = -1;
for (int i = 1; i <= n; i++) {
arr[i] = fast.nextInt();
sum += Math.abs(arr[i]);
}
int len = 0;
for (int i = 1; i <= n; i++) {
if (arr[i] < 0) {
len++;
while(i <= n && arr[i] <= 0) {
i++;
}
}
}
fast.write(sum + " " + len + "\n");
}
fast.flush();
}
C题
题意:给一个节点,要求求出根节点到该节点的路径和。
向上递归即可,n == 1为递归出口。
private static void problemC(Fast fast) {
int t = fast.nextInt();
while(--t >= 0) {
var n = fast.nextLong();
long sum = dfs(n);
fast.write(sum + "\n");
}
fast.flush();
}
private static long dfs(long num) {
if (num == 1) {
return 1;
}
return num + dfs(num >> 1);
}
D题
题意,给一颗没有循环和多条边的树,查询给出的两个节点到叶子节点的可能路径的乘积。
按照树从上往下的形式记忆化搜索即可。
private static void problemD(Fast fast) {
int t = fast.nextInt();
while(--t >= 0) {
int n = fast.nextInt();
HashMap<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < n - 1; i++) {
int x = fast.nextInt();
int y = fast.nextInt();
map.computeIfAbsent(x, list -> new ArrayList<>()).add(y);
map.computeIfAbsent(y, list -> new ArrayList<>()).add(x);
}
boolean[] vis = new boolean[n + 1];
var nodeMap = new HashMap<Integer, Long>();
dfsNode(1, nodeMap, vis, map);
int p = fast.nextInt();
for (int i = 0; i < p; i++) {
int x = fast.nextInt();
int y = fast.nextInt();
fast.write((long)nodeMap.get(x) * nodeMap.get(y) + "\n");
}
fast.flush();
}
}
private static long dfsNode(int parent, HashMap<Integer, Long> nodeMap, boolean[] vis, HashMap<Integer, List<Integer>> map) {
vis[parent] = true;
var list = new ArrayList<Integer>();
for (Integer children : map.get(parent)) {
if (!vis[children]) {
list.add(children);
}
}
if (list.size() == 0) {
nodeMap.put(parent, (long)1);
return 1;
}
long sum = 0;
for (var children : list) {
sum += dfsNode(children, nodeMap, vis, map);
}
nodeMap.put(parent, sum);
return sum;
}
E题
题意:1 - N的元素中,给出M条线段,Q次update操作,问在第几次update操作之后,N条线段中有一条线段1的元素比0的元素多。
update操作为将一个元素的值变成1。
可用线段树维护每次update操作之后的区间信息,再去查有没有符合的线段。但是这样时间复杂度为log2(N) * M * Q
可以通过对Q进行二分查询,减少时间复杂度。
代码如下:
private static void problemE(Fast fast) {
int t = fast.nextInt();
while(--t >= 0) {
int n = fast.nextInt();
int m = fast.nextInt();
var list = new ArrayList<int[]>();
for (int i = 0; i < m; i++) {
list.add(new int[]{fast.nextInt(), fast.nextInt()});
}
int q = fast.nextInt();
int index = -1;
List<Integer> listQ = new ArrayList<>();
for (int i = 0; i < q; i++) {
listQ.add(fast.nextInt());
}
int min = Integer.MAX_VALUE;
int l = 1, r = q;
go: while(l <= r) {
SegTree segTree = new SegTree(n);
int mid = (l + r) >> 1;
for (int i = 0; i < mid; i++) {
segTree.updateNodeVal(listQ.get(i), 1);
}
for (int[] ints : list) {
int len = segTree.queryRange(ints[0], ints[1]);
if (len > ((ints[1] - ints[0] + 1) >> 1)) {
min = Math.min(mid, min);
r = mid - 1;
continue go;
}
}
l = mid + 1;
}
if (min == Integer.MAX_VALUE) {
min = -1;
}
fast.write(min + "\n");
}
fast.flush();
}
class SegTree {
private SegNode root;
private final SegNode[] nodes;
private final int N;
public SegTree(int len) {
N = len;
nodes = new SegNode[(N << 2) + 5];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = new SegNode();
}
}
public static class SegNode {
int val = 0;
}
public void updateNodeVal(int k, int val) {
changeNodeVal(k, val, 1, N, 1);
}
private void changeNodeVal(int k, int val, int l, int r, int id) {
if (l == r) {
nodes[id].val = val;
return;
}
var mid = (l + r) >> 1;
int left = id << 1;
int right = id << 1 | 1;
if (k <= mid) {
changeNodeVal(k, val, l, mid, left);
} else {
changeNodeVal(k, val, mid + 1, r, right);
}
pullUp(id, left, right);
}
private void pullUp(int id, int left, int right) {
nodes[id].val = nodes[left].val + nodes[right].val;
}
public int queryRange(int l, int r) {
return findRange(l, r, 1, N, 1);
}
private int findRange(int l, int r, int L, int R, int id) {
if (L > r || R < l) {
return 0;
}
if (L >= l && R <= r) {
return nodes[id].val;
}
var mid = (L + R) >> 1;
int sum = 0;
if (mid >= l) {
sum += findRange(l, r, L, mid, id << 1);
}
if (mid < r) {
sum += findRange(l, r, mid + 1, R, id << 1 | 1);
}
return sum;
}
}
F题
题意:有两个操作,一个是添加新节点,并且给予节点权值。一个是查询两个节点之间的某一连续子节点是否满足value,如果满足输出yes。
首先可以想到,添加新节点并不会影响到之前节点的查询操作,所以我们优先把节点的添加操作都执行完毕,再去离线查询两个节点之间是否符合题意。
因每次都是添加新节点,所以节点与节点之间不可能形成环,我们可以先把它转换成一颗多叉树,又因为每个节点的权值为{1, -1}。那么题目就变成了在一颗树上,查询value是否大于等于节点之间的最小值 & 小于等于节点之间的最大值。
题目就变成了维护两个节点所形成的区间信息。我们使用树链剖分,先把整颗树完整的剖分成N个重链,因树链剖分的特性,所有重链的每个元素编号恰好是1到所有重链的总长度和。因此我们对这一长度做线段树的初始化,两个节点的区间查询视为多个重链合并的区间查询,完整代码如下:
private static void problemF(Fast fast) {
int t = fast.nextInt();
while (--t >= 0) {
int n = fast.nextInt();
var val = new int[n + 5];
val[1] = 1;
var cnt = 1;
var map = new HashMap<Integer, List<Integer>>();
var queries = new ArrayList<int[]>();
while (--n >= 0) {
String[] split = fast.nextLine().split(" ");
if ("+".equals(split[0])) {
val[++cnt] = Integer.parseInt(split[2]);
int f = Integer.parseInt(split[1]);
map.computeIfAbsent(cnt, list -> new ArrayList<>()).add(f);
map.computeIfAbsent(f, list -> new ArrayList<>()).add(cnt);
} else {
queries.add(new int[]{Integer.parseInt(split[1]), Integer.parseInt(split[2]), Integer.parseInt(split[3])});
}
}
if (map.size() == 0) {
for (var query : queries) {
if (query[2] == 0 || query[2] == 1) {
fast.write("YES\n");
} else {
fast.write("NO\n");
}
}
continue;
}
TreeChainPartitioning partitioning = new TreeChainPartitioning(cnt, map, val);
SegTreeE tree = new SegTreeF(partitioning);
for (var query : queries) {
int u = query[0];
int v = query[1];
int value = query[2];
var node = tree.findRangeByNode(u, v);
if (value == 0 || node.mx >= value && node.mn <= value) {
fast.write("YES\n");
} else {
fast.write("NO\n");
}
}
}
fast.flush();
}
private static class SegTreeF {
private final SegNode[] nodes;
private final int N;
private final int[] val;
TreeChainPartitioning partitioning;
public SegTreeE(TreeChainPartitioning partitioning) {
this.partitioning = partitioning;
N = partitioning.val.length;
this.val = partitioning.val;
nodes = new SegNode[(N << 2) + 5];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = new SegNode();
}
build(1, N - 1, 1);
}
private void build(int l, int r, int id) {
if (l == r) {
nodes[id].lmx = val[l];
nodes[id].lmn = val[l];
nodes[id].rmx = val[l];
nodes[id].rmn = val[l];
nodes[id].mn = val[l];
nodes[id].mx = val[l];
nodes[id].sum = val[l];
return;
}
int mid = (l + r) >> 1;
int left = id << 1;
int right = id << 1 | 1;
build(l, mid, left);
build(mid + 1, r, right);
pullUp(nodes[id], nodes[id << 1], nodes[id << 1 | 1]);
}
public static class SegNode {
long mn = Integer.MAX_VALUE;
long mx = Integer.MIN_VALUE;
long lmn = Integer.MAX_VALUE;
long lmx = Integer.MIN_VALUE;
long rmn = Integer.MAX_VALUE;
long rmx = Integer.MIN_VALUE;
long sum = 0;
}
private void pullUp(SegNode node, SegNode left, SegNode right) {
var lsum = left.sum;
var rsum = right.sum;
node.sum = left.sum + right.sum;
node.mn = Math.min(left.mn, Math.min(right.mn, left.rmn + right.lmn));
node.mx = Math.max(left.mx, Math.max(right.mx, left.rmx + right.lmx));
node.lmn = Math.min(left.lmn, lsum + right.lmn);
node.lmx = Math.max(left.lmx, lsum + right.lmx);
node.rmn = Math.min(right.rmn, rsum + left.rmn);
node.rmx = Math.max(right.rmx, rsum + left.rmx);
}
public SegNode queryRange(int l, int r) {
return findRange(l, r, 1, N - 1, 1);
}
private SegNode findRange(int l, int r, int L, int R, int id) {
SegNode res = new SegNode();
if (L >= l && R <= r) {
pullUp(res, nodes[id], res);
return res;
}
var mid = (L + R) >> 1;
if (mid >= r) {
return findRange(l, r, L, mid, id << 1);
}
if (l > mid) {
return findRange(l, r, mid + 1, R, id << 1 | 1);
}
pullUp(res, findRange(l, r, L, mid, id << 1), findRange(l, r, mid + 1, R, id << 1 | 1));
return res;
}
private SegNode findRangeByNode(int u, int v) {
var left = new SegNode();
var right = new SegNode();
while(partitioning.top[u] != partitioning.top[v]) {
if (partitioning.deep[partitioning.top[u]] < partitioning.deep[partitioning.top[v]]) {
int t = u;
u = v;
v = t;
var c = left;
left = right;
right = c;
}
SegNode node = queryRange(partitioning.dfn[partitioning.top[u]], partitioning.dfn[u]);
pullUp(left, node, left);
u = partitioning.father[partitioning.top[u]];
}
if (partitioning.deep[u] < partitioning.deep[v]) {
int t = u;
u = v;
v = t;
var c = left;
left = right;
right = c;
}
SegNode res = queryRange(partitioning.dfn[v], partitioning.dfn[u]);
pullUp(left, res, left);
var t = left.lmx;
left.lmx = left.rmx;
left.rmx = t;
t = left.lmn;
left.lmn = left.rmn;
left.rmn = t;
var ans = new SegNode();
pullUp(ans, left, right);
return ans;
}
}
private static class TreeChainPartitioning {
int[] size;
int[] son;
int[] deep;
int[] father;
int[] top;
int[] dfn;
int[] rnk;
int cnt;
int[] val;
Map<Integer, List<Integer>> map;
public TreeChainPartitioning(int len, Map<Integer, List<Integer>> map, int[] val) {
size = new int[len + 1];
son = new int[len + 1];
deep = new int[len + 1];
father = new int[len + 1];
top = new int[len + 1];
dfn = new int[len + 1];
rnk = new int[len + 1];
this.val = new int[len + 1];
cnt = 0;
this.map = map;
dfs1(1);
dfs2(1, 1);
for (int i = 1; i <= len; i++) {
this.val[i] = val[rnk[i]];
}
}
private void dfs1(int u) {
size[u] = 1;
for(var v : map.get(u)) {
if(v == father[u]) {
continue;
}
deep[v] = deep[u] + 1;
father[v] = u;
dfs1(v);
size[u] += size[v];
if (son[u] == 0 || size[v] > size[son[u]]) {
son[u] = v;
}
}
}
void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++cnt;
rnk[cnt] = u;
if (son[u] == 0) {
return;
}
dfs2(son[u], t);
for (var v : map.get(u)) {
if (v != son[u] && v != father[u]) {
dfs2(v, v);
}
}
}
}