牛客周赛 Round 88
写在前面
比赛地址:https://ac.nowcoder.com/acm/contest/106318。
因为各种原因,赛时全程用的 java,这次显然熟练不少呃呃。
以下再记一点 java 的算法题语法。
A
水题,直接做。
如下是一种适合算法题的比较快的 IO 方式:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static int a, b, c;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
a = (int) in.nval;
in.nextToken();
b = (int) in.nval;
in.nextToken();
c = (int) in.nval;
out.println((c >= a * b) ? "YES" : "NO");
out.flush();
out.close();
br.close();
}
}
B
求得三个中点,并判断中点与对应的顶点是否均位于坐标轴上即可。
import java.io.*;
public class Main {
public static int xa, ya, xb, yb, xc, yc;
public static boolean flag;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int T = (int) in.nval;
while (T > 0) {
-- T;
in.nextToken();
xa = (int) in.nval;
in.nextToken();
ya = (int) in.nval;
in.nextToken();
xb = (int) in.nval;
in.nextToken();
yb = (int) in.nval;
in.nextToken();
xc = (int) in.nval;
in.nextToken();
yc = (int) in.nval;
flag = false;
if ((xa + xb) % 2 == 0) {
int xab = (xa + xb) / 2;
if (xc == 0 && xab == 0) flag = true;
}
if ((ya + yb) % 2 == 0) {
int yab = (ya + yb) / 2;
if (yc == 0 && yab == 0) flag = true;
}
if ((xa + xc) % 2 == 0) {
int xac = (xa + xc) / 2;
if (xb == 0 && xac == 0) flag = true;
}
if ((ya + yc) % 2 == 0) {
int yac = (ya + yc) / 2;
if (yb == 0 && yac == 0) flag = true;
}
if ((xb + xc) % 2 == 0) {
int xbc = (xb + xc) / 2;
if (xa == 0 && xbc == 0) flag = true;
}
if ((yb + yc) % 2 == 0) {
int ybc = (yb + yc) / 2;
if (ya == 0 && ybc == 0) flag = true;
}
out.println(flag ? "YES" : "NO");
}
out.flush();
out.close();
br.close();
}
}
C
同上,求得三个中点,并判断中点与对应的顶点是否某一维坐标相同即可,若相同则说明中线与这一维的坐标轴平行。
import java.io.*;
public class Main {
public static int xa, ya, xb, yb, xc, yc;
public static boolean flag;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int T = (int) in.nval;
while (T > 0) {
-- T;
in.nextToken();
xa = (int) in.nval;
in.nextToken();
ya = (int) in.nval;
in.nextToken();
xb = (int) in.nval;
in.nextToken();
yb = (int) in.nval;
in.nextToken();
xc = (int) in.nval;
in.nextToken();
yc = (int) in.nval;
flag = false;
if ((xa + xb) % 2 == 0) {
int xab = (xa + xb) / 2;
if (xc == xab) flag = true;
}
if ((ya + yb) % 2 == 0) {
int yab = (ya + yb) / 2;
if (yc == yab) flag = true;
}
if ((xa + xc) % 2 == 0) {
int xac = (xa + xc) / 2;
if (xb == xac) flag = true;
}
if ((ya + yc) % 2 == 0) {
int yac = (ya + yc) / 2;
if (yb == yac) flag = true;
}
if ((xb + xc) % 2 == 0) {
int xbc = (xb + xc) / 2;
if (xa == xbc) flag = true;
}
if ((yb + yc) % 2 == 0) {
int ybc = (yb + yc) / 2;
if (ya == ybc) flag = true;
}
out.println(flag ? "YES" : "NO");
}
out.flush();
out.close();
br.close();
}
}
D
一个显然的想法是考虑找到 \(x\) 中不大于 \(x\) 的二进制为 0 的位 \(i\),满足 \(2^i < x\),则令 \(z = 2^i\) 一定合法。
然后考虑上述构造的必要性:若 \(x\) 中所有二进制位均为 1,则选择任意 \(1\le z < x\) 均会出现进位,导致 \(x+z\) 中至少某一位上一定不为 1,则一定不合法。
注意:Java 的 long
类型常量需要使用 L
作为后缀,若赋值给 int
的常量超出范围且未使用 L
后缀标记为 long
类型,直接编译失败。
import java.io.*;
public class Main {
public static long x, ans;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int T = (int) in.nval;
while (T > 0) {
-- T;
in.nextToken();
x = (long) in.nval;
ans = -1;
for (int i = 0; i < 62; ++ i) {
if ((1L << i) >= x) break;
if ((x >> i & 1) == 0) {
ans = (1L << i);
break;
}
}
out.println(ans);
}
out.flush();
out.close();
br.close();
}
}
E
套路 DP。
记 \(f_{i, 0/1}\) 表示当前位于位置 \(i\),且位于表世界/里世界时,可以获得的最多金币数量。初始化 \(f_{i, 0/1} = -\infin\),\(f_{1, 0} = a_i\),转移时枚举上一步的位置,则有:
答案即为 \(\max(f_{n, 0}, f_{n , 1})\)。
import java.io.*;
public class Main {
public static final long inf = (long) 1e18;
public static int n;
public static long k;
public static long[][] a;
public static long[][] f;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int)in.nval;
in.nextToken();
k = (long)in.nval;
a = new long[n + 1][2];
f = new long[n + 1][2];
for (int i = 1; i <= n; i++) {
in.nextToken();
a[i][0] = (long) in.nval;
}
for (int i = 1; i <= n; i++) {
in.nextToken();
a[i][1] = (long) in.nval;
}
for (int i = 1; i <= n; i++) {
f[i][0] = f[i][1] = -inf;
}
f[1][0] = a[1][0];
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= 1; j++) {
f[i][j] = Math.max(f[i][j], f[i - 1][j] + a[i][j]);
if (f[i - 1][j ^ 1] >= k) {
f[i][j] = Math.max(f[i][j], f[i - 1][j ^ 1] - k + a[i][j]);
}
}
}
out.println(Math.max(f[n][0], f[n][1]));
out.flush();
out.close();
br.close();
}
}
F
套路构造。
先特判全 0 的前缀和后缀的构造,仅需找到第一个/最后一个非 0 位置,并以该位置为起点,按序构造递增的前缀/后缀即可;然后考虑枚举所有相邻的非 0 位置 \([L, R]\),满足 \(a_L\not= 0, a_{R} \not= 0, a_{i} = 0(L < i < R)\),判断能否在其中填上若干个 0 使 \(a_L\sim a_R\) 合法。
容易发现题目要求的 \(|a_i - a_{i - 1}| = 1\),实际上是钦定了数列各位置上的值的奇偶性,满足下班奇偶性相同的位置上的权值奇偶性相同,否则相反。则可以得到上述枚举的 \([L, R]\) 存在合法的构造当且仅当:
- \(R - L \equiv |a_R - a_L| \pmod 2\)
- \(|a_R - a_L| \le R - L\)。
若上述条件成立,则一种比较简单的构造是从左往右构造 \(a_{L + 1} \sim a_{R - 1}\),设当前构造到 \(a_i\),若 \(a_{i - 1}\ge a_R\) 则令 \(a_{i} = a_{i - 1} - 1\),否则令 \(a_{i} = a_{i - 1} + 1\),显然可以保证构造一定合法。
实际上没观察出上述合法条件也没问题,直接按照上述构造嗯做,最后再检查合法性即可。
import java.io.*;
import java.util.ArrayList;
public class Main {
public static final int inf = (int) 1e9;
public static int n;
public static int[] a;
public static boolean flag;
public static void check(int L_, int R_) {
if (L_ == 0) {
for (int i = R_ - 1; i > 0; -- i) a[i] = a[i + 1] + 1;
return ;
}
if (R_ == n + 1) {
for (int i = L_ + 1; i <= n; ++ i) a[i] = a[i - 1] + 1;
return ;
}
if ((R_ - L_) % 2 != Math.abs(a[R_] - a[L_]) % 2) {
flag = false;
return ;
}
if ((R_ - L_) < Math.abs(a[R_] - a[L_])) {
flag = false;
return ;
}
for (int i = L_ + 1; i <= R_ - 1; ++ i) {
if (a[R_] >= a[i - 1]) {
a[i] = a[i - 1] + 1;
} else {
a[i] = a[i - 1] - 1;
}
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int)in.nval;
flag = true;
a = new int[n + 2];
for (int i = 1; i <= n; ++ i) {
in.nextToken();
a[i] = (int)in.nval;
}
int lst = 0;
a[0] = a[n + 1] = 1;
for (int i = 1; i <= n + 1; ++i) {
if (a[i] != 0) {
check(lst, i);
lst = i;
}
}
if (!flag) {
out.println(-1);
} else {
for (int i = 1; i <= n; ++ i) {
out.print(a[i] + " ");
}
}
out.flush();
out.close();
br.close();
}
}
G
树,线段树
发现要用 Java 写线段树的时候觉得好他妈麻烦本来都想直接弃赛了,以为要写一万年的居然意外地没写挂呃呃呃呃
先 dfs \(\operatorname{sum}_u\) 表示路径 \(1\rightarrow u\) 的权值之和,则 \(\max_u \operatorname{sum}_{u}\) 即为答案。
容易发现一次操作 \((x, y)\) 仅会影响 \(x\) 的子树中所有点 \(u\) 的 \(\operatorname{sum}_u\),具体影响是令它们的 \(\operatorname{sum}_u\) 减去 \(\operatorname{sum}_{\operatorname{fa}_x}\),再加上 \(\operatorname{sum}_y\)。发现上述影响相当于做了一次子树加,于是套路地求 dfs 序把原树拍扁成序列。记节点 \(u\) 的子树在 dfs 序序列中对应区间 \([L_u, R_u]\),并得到序列 \(s\) 满足 \(\operatorname{sum}_{u} = s_{L_u}\)。
考虑用线段树维护序列 \(s\) 的区间最大值,则一次操作 \((x, y)\) 相当于令区间 \([L_x, L_y]\) 加 \(\operatorname{sum}_y - \operatorname{sum}_{\operatorname{fa}_x}\),答案即 \(s\) 的全局最大值。
由于每次操作对原树的影响都是独立的,于是每次操作后求得 \(s\) 的全局最大值再做一次还原操作即可。总时间复杂度 \(O((n + q)\log n)\) 级别。
实际上可以通过直接预处理前后缀最大值和子树最大值来避免使用线段树动态修改。详见官方题解:https://blog.nowcoder.net/n/a0c38ae00dfd4a6cb16f2f7c33fe3c50。
import java.io.*;
import java.util.ArrayList;
public class Main {
public static final long inf = (long) 1e18;
public static int n, q, dfnnum;
public static long k;
public static int[] fa, dfn, L, R;
public static long[] a, sum;
public static ArrayList<Integer>[] sons;
public static class seg {
public static long[] t, tag;
private static int ls(int x_) {
return x_ << 1;
}
private static int rs(int x_) {
return x_ << 1 | 1;
}
private static int mid(int L_, int R_) {
return (L_ + R_) >> 1;
}
public static void pushup(int now_) {
t[now_] = Math.max(t[ls(now_)], t[rs(now_)]);
}
public static void pushdown(int now_) {
long t = tag[now_];
tag[ls(now_)] += t;
tag[ls(now_)] += t;
tag[rs(now_)] += t;
tag[rs(now_)] += t;
tag[now_] = 0;
}
public static void init() {
t = new long[4 * n + 1];
tag = new long[4 * n + 1];
build(1, 1, n);
}
public static void build(int now_, int L_, int R_) {
tag[now_] = 0;
if (L_ == R_) {
t[now_] = sum[dfn[L_]];
return ;
}
build(ls(now_), L_, mid(L_, R_));
build(rs(now_), mid(L_, R_) + 1, R_);
pushup(now_);
}
public static void modify(int now_, int L_, int R_, int l_, int r_, long val_) {
if (l_ <= L_ && R_ <= r_) {
t[now_] += val_;
tag[now_] += val_;
return ;
}
if (tag[now_] != 0) pushdown(now_);
if (l_ <= mid(L_, R_)) modify(ls(now_), L_, mid(L_, R_), l_, r_, val_);
if (r_ > mid(L_, R_)) modify(rs(now_), mid(L_, R_) + 1, R_, l_, r_, val_);
pushup(now_);
}
public static long getMax() {
return t[1];
}
}
public static void dfs(int u_, int fa_) {
dfn[++ dfnnum] = u_;
L[u_] = dfnnum;
sum[u_] = sum[fa_] + a[u_];
for (int v_: sons[u_]) {
if (v_ == fa_) continue;
dfs(v_, u_);
}
R[u_] = dfnnum;
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n = (int)in.nval;
in.nextToken();
q = (int)in.nval;
dfnnum = 0;
fa = new int[n + 1];
dfn = new int[n + 1];
L = new int[n + 1];
R = new int[n + 1];
a = new long[n + 1];
sum = new long[n + 1];
sons = new ArrayList[n + 1];
for(int i = 1; i <= n; ++ i) {
in.nextToken();
sons[i] = new ArrayList<>();
a[i] = (long) in.nval;
}
for (int i = 2; i <= n; ++ i) {
in.nextToken();
fa[i] = (int)in.nval;
sons[fa[i]].add(i);
}
dfs(1, 0);
seg.init();
for (int i = 1; i <= q; ++ i) {
int x, y;
in.nextToken();
x = (int)in.nval;
in.nextToken();
y = (int)in.nval;
seg.modify(1, 1, n, L[x], R[x], sum[y] -sum[fa[x]]);
out.println(seg.getMax());
seg.modify(1, 1, n, L[x], R[x], sum[fa[x]] - sum[y]);
}
out.flush();
out.close();
br.close();
}
}
写在最后
未来何去何从?
夹带私货之来点经典老番: