前言
题解
本科组的难度就很适宜,有区分度,做起来很舒服。

移动移动移动
难度:钻石
思路:组合数学
数学解
r
e
s
=
C
(
x
+
y
,
x
)
∗
(
p
/
100
)
x
∗
(
(
100
−
p
)
/
100
)
y
res= C(x+y, x) * (p/100)^x * ((100-p)/100)^y
res=C(x+y,x)∗(p/100)x∗((100−p)/100)y
变型得 r e s = C ( x + y , x ) ∗ p x ∗ ( 100 − p ) y / 10 0 ( x + y ) 变型得res = C(x+y, x) * p^x * (100-p)^y / 100^{(x+y)} 变型得res=C(x+y,x)∗px∗(100−p)y/100(x+y)
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main {
static boolean quick(int x1, int y1, int x2, int y2, int n) {
if (x2 < x1 || y2 < y1) return false;
int d = Math.abs(x2 - x1) + Math.abs(y2 - y1);
if (d != n) return false;
return true;
}
static long ksm(long b, long v, long m) {
long r = 1;
while (v > 0) {
if (v % 2 == 1) {
r = r * b % m;
}
b = b * b % m;
v /= 2;
}
return r;
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int MX = 300000; // 3 * 10^5
long mod = 998244353l;
long[] fac = new long[MX + 1];
long[] inv = new long[MX + 1];
fac[0] = 1;
for (int i = 1; i <= MX; i++) {
fac[i] = fac[i - 1] * i % mod;
}
inv[MX] = ksm(fac[MX], mod - 2, mod);
for (int i = MX - 1; i >= 0; i--) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
int t = sc.nextInt();
while (t-- > 0) {
int x1 = sc.nextInt(), y1 = sc.nextInt();
int x2 = sc.nextInt(), y2 = sc.nextInt();
int n = sc.nextInt();
int p = sc.nextInt(), q = sc.nextInt();
if (!quick(x1, y1, x2, y2, n)) {
System.out.println(0);
} else {
int dx = x2 - x1;
int dy = y2 - y1;
int ty = dy + dx;
long fz = fac[ty] * inv[dx] % mod *inv[dy] % mod;
long fz1 = ksm(p, dx, mod);
long fz2 = ksm(q, dy, mod);
fz = fz * fz1 % mod * fz2 % mod;
long fm = ksm(100, ty, mod);
long res = fz * ksm(fm, mod - 2, mod) % mod;
System.out.println(res);
}
}
}
}
请相信我会做图论
难度:钻石
思路:贪心
题目很俏皮,其他已经变相告诉你这题和图论算法没啥特别关系。
我会等差数列
难度: 钻石
思路: 前缀和
我会修改图
难度: 星耀
思路:逆序建边 + 并查集 + 启发式合并
对于拆边,可以逆向思维按建边来处理。
这样并查集为维护连通性,魔改并查集,引入启发式合并。
启发式合并的核心 : 大的合并小的 启发式合并的核心: 大的合并小的 启发式合并的核心:大的合并小的
import java.io.BufferedInputStream;
import java.util.*;
import java.util.stream.Collectors;
public class Main {
static class Dsu {
int n;
int[] arr;
Map<Integer, Integer> []hp;
public Dsu(int n, int[] ws) {
this.n = n;
this.arr = new int[n + 1];
hp = new Map[n + 1];
for (int i = 1; i <= n; i++) {
hp[i] = new TreeMap<>();
hp[i].merge(ws[i], 1, Integer::sum);
}
}
int find(int u) {
if (arr[u] == 0) return u;
return arr[u] = find(arr[u]);
}
void merge(int u, int v) {
int a = find(u);
int b = find(v);
if (a != b) {
// merge
if (hp[a].size() >= hp[b].size()) {
int t = a;
a = b;
b = t;
}
for (Map.Entry<Integer, Integer> env: hp[a].entrySet()) {
hp[b].merge(env.getKey(), env.getValue(), Integer::sum);
}
arr[a] = b;
}
}
int query(int p, int v, int y) {
int other = y - v;
if (other != v) {
return hp[find(p)].getOrDefault(other, 0);
} else {
return hp[find(p)].getOrDefault(other, 0) - 1;
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt(), m = sc.nextInt(), q = sc.nextInt();
int[] arr = new int[n + 1];
for (int i = 1; i <= n; i++) {
arr[i] = sc.nextInt();
}
boolean[] vis = new boolean[m];
List<int[]> edges = new ArrayList<>();
for (int i = 0; i < m; i++) {
int u = sc.nextInt(), v = sc.nextInt();
edges.add(new int[] {u, v});
}
List<int[]> ops = new ArrayList<>();
for (int i = 0; i < q; i++) {
int op = sc.nextInt();
if (op == 1) {
int id = sc.nextInt() - 1;
vis[id] = true; // del
ops.add(new int[] {op, id});
} else {
int a = sc.nextInt();
int b = sc.nextInt();
ops.add(new int[] {op, a, b});
}
}
Dsu dsu = new Dsu(n, arr);
for (int i = 0; i < m; i++) {
if (!vis[i]) {
int[] e = edges.get(i);
dsu.merge(e[0], e[1]);
}
}
// 逆序处理
List<Integer> res = new ArrayList<>();
for (int i = ops.size() - 1; i >= 0; i--) {
int[] cur = ops.get(i);
if (cur[0] == 1) {
int[] e = edges.get(cur[1]);
dsu.merge(e[0], e[1]);
} else {
int a = cur[1];
int y = cur[2];
res.add(dsu.query(a, arr[a], y));
}
}
Collections.reverse(res);
System.out.println(res.stream().map(String::valueOf).collect(Collectors.joining("\n")));
}
}
团队能量
难度: 钻石
思路: DP
这题很容易被带偏,以为是一道基于RMQ优化的DP
从转移公式入手
d p [ j ] = max i = j − k i = j − 1 { d p [ i ] + m a x ( a r r [ i + 1 : j ] ) ∗ ( j − i ) − s u m ( a r r [ i + 1 : j ] ) } dp[j] = \max_{i=j-k}^{i=j-1} \{dp[i] + max(arr[i+1:j]) * (j - i) - sum(arr[i+1:j]) \} dp[j]=i=j−kmaxi=j−1{dp[i]+max(arr[i+1:j])∗(j−i)−sum(arr[i+1:j])}
如果引入前缀和 p r e [ i + 1 ] = p r e [ i ] + a r r [ i ] pre[i+1]=pre[i]+arr[i] pre[i+1]=pre[i]+arr[i]
d p [ j ] = max i = j − k i = j − 1 { d p [ i ] + m a x ( a r r [ i + 1 : j ] ) ∗ ( j − i ) − ( p r e [ j + 1 ] − p r e [ i + 1 ] ) } dp[j] = \max_{i=j-k}^{i=j-1} \{dp[i] + max(arr[i+1:j]) * (j - i) - (pre[j+1] - pre[i + 1]) \} dp[j]=i=j−kmaxi=j−1{dp[i]+max(arr[i+1:j])∗(j−i)−(pre[j+1]−pre[i+1])}
难在 m a x ( a r r [ i + 1 : j ] ) max(arr[i+1:j]) max(arr[i+1:j]), 它不能拆分,使得这个转化公式转发为
左侧和 j 有关 = 右侧和 i 有关的转移公式 左侧和j有关 = 右侧和i有关 的转移公式 左侧和j有关=右侧和i有关的转移公式
如果转化不了,则避免不了时间复杂度退化为 O ( n 2 ) O(n^2) O(n2)
所以这条路是行不通的。
回到 m a x ( a r r [ i + 1 : j ] − s u m ( a r r [ i + 1 : j ] ) 最小化这个核心问题 max(arr[i+1:j] - sum(arr[i+1:j]) 最小化这个核心问题 max(arr[i+1:j]−sum(arr[i+1:j])最小化这个核心问题
是否存在优化的地方呢?
这里存在一个贪心策略,
即区间越小,拆分的越多, m a x ( a r r [ i + 1 : j ] − s u m ( a r r [ i + 1 : j ] ) 越小 即区间越小,拆分的越多,max(arr[i+1:j] - sum(arr[i+1:j])越小 即区间越小,拆分的越多,max(arr[i+1:j]−sum(arr[i+1:j])越小
因此每个区间长度必然为[2, min(3, k)]之间
这样的话,这个DP就很容易写了。
核心在于发现
基于贪心优化的 D P 基于贪心优化的DP 基于贪心优化的DP
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
int main() {
int n, k;
cin >> n >> k;
vector<int64> arr(n);
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
sort(arr.begin(), arr.end());
int64 inf = 0x3f3f3f3f3f3f3f;
int r = min(k , 3);
// 贪心即可
vector<int64> dp(n + 1, inf);
dp[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = 2; j <= r; j++) {
if (i - j + 1 >= 0) {
int64 mz = max(arr[i], arr[i - 1]);
int64 acc = arr[i] + arr[i - 1];
if (j > 2) {
mz = max(mz, arr[i - 2]);
acc += arr[i - 2];
}
dp[i + 1] = min(dp[i + 1], dp[i - j + 1] + j * mz - acc);
}
}
}
cout << (dp[n] == inf ? -1 : dp[n]) << endl;
return 0;
}
异或
难度: 星耀
思路: 离线计算 + 树上差分/计数
对于任意二元组的异或和,需要使用到
拆位技巧 拆位技巧 拆位技巧
∑ i = 0 i = 30 ( z − z i ( 1 ) ) ∗ ( z − z i ( 0 ) ) ∗ ( 1 < < i ) \sum_{i=0}^{i=30} (z - z_i(1)) * (z - z_i(0)) * (1 << i) i=0∑i=30(z−zi(1))∗(z−zi(0))∗(1<<i)
至于不超过k距离的限制,这个属于树上差分的技巧
#include <bits/stdc++.h>
using namespace std;
struct T {
T() {
cnt = 0;
memset(arr, 0, sizeof(arr));
}
void add(int v) {
cnt += 1;
for (int i = 0; i < 30; i++) {
if ((v & (1<<i)) !=0) arr[i]++;
}
}
void sub(int v) {
cnt -= 1;
for (int i = 0; i < 30; i++) {
if ((v & (1<<i)) !=0) arr[i]--;
}
}
void merge(T &rhs) {
cnt += rhs.cnt;
for (int i = 0; i < 30; i++) {
arr[i] += rhs.arr[i];
}
}
long long compute(long long mod) {
long long res = 0;
for (int i = 0; i < 30; i++) {
res += (long long)arr[i] * (cnt - arr[i]) % mod * (1ll << i) % mod;
res %= mod;
}
return res;
}
int cnt;
int arr[30];
};
int main() {
int n, q, k;
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> q >> k;
vector<int> w(n);
vector<vector<int>> g(n);
for (int &x: w) cin >> x;
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--; v--;
g[u].push_back(v);
g[v].push_back(u);
}
long long mod = (long long)1e9 + 7;
vector<vector<int>> gx(n);
vector<long long> ans(n);
function<T(int,int,vector<int>&stk)> dfs;
dfs = [&](int u, int fa, vector<int> &stk) {
T res;
res.add(w[u]);
stk.push_back(u);
for (int v: g[u]) {
if (v == fa) continue;
T cr = dfs(v, u, stk);
res.merge(cr);
}
ans[u] = res.compute(mod);
int nz = stk.size();
if (nz - k - 1 >= 0) {
gx[stk[nz - k - 1]].push_back(u);
}
for (int son: gx[u]) {
res.sub(w[son]);
}
stk.pop_back();
return res;
};
vector<int> stk;
dfs(0, -1, stk);
for (int i = 0; i < q; i++) {
int u;
cin >> u;
u--;
cout << ans[u] << '\n';
}
return 0;
}