The 2023 ICPC Asia Hangzhou Regional Contest
順序按個人難度
M. V-Diagram
M. 題意
有一個長度$n$的$v$形數組,求包含最低點$i$的最大的區間平均值。
n ~ 3e5
a ~ 1e9
觀察題。不妨把數組以$i$拆成兩。假設這個平均值是$k$,對於左邊的端點,如果這個點的值大於$k$,把端點向左移會使平均值更優;反之,向右移會使平均值更優。所有區間都能歸納到$[1, i+1]$, $[i-1, n]$, $[1, n]$, $[i-1, i+1]$這四種情況。最後一種情況顯然不會是最大值,可以不用算。
複雜度 $O(n)$。
M. 代碼
#include "bits/stdc++.h"
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define LNF 0x3f3f3f3f3f3f3f3fll
#define MOD 998244353
int main() {
#ifdef TEST
freopen("zz.in", "r+", stdin);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t; cin >> t;
cout << setprecision(18) << fixed;
while (t --) {
int n; cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++ i) cin >> a[i];
int pos = 0;
for (int i = 0; i < n - 1; ++ i) if (a[i] > a[i+1]) pos = i+1;
vector<double> sum(3, 0);
for (int i = 0; i < n; ++ i) {
sum[0] += a[i];
sum[1] += (i <= pos+1) * a[i];
sum[2] += (i >= pos-1) * a[i];
}
double res = max(sum[0] / n, max(sum[1] / (pos+2), sum[2] / (n-(pos+1)+2)));
cout << res << '\n';
}
}
J. Mysterious Tree
J. 題意
有顆$n$個節點的樹,可以詢問$\lceil{\frac{n}{2}}\rceil + 3$次邊的連接情況,判斷樹是鏈還是星。
p ~ 1e3
觀察題。注意到星形樹的所有邊都連接到根節點,枚舉$\lfloor \frac{n}{2}\rfloor$個不相交的數對可以判斷出可行的星形樹根(最多兩個),然後隨便選另外兩或三個點判斷一下跟根節點連的邊就可以判斷是不是星。
複雜度是 $O(n)$ 。
J. 代碼
#include "bits/stdc++.h"
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define LNF 0x3f3f3f3f3f3f3f3fll
#define MOD 998244353
bool qry(int u, int v) {
cout << "? " << u << " " << v << '\n';
cout.flush();
bool res; cin >> res;
return res;
}
int main() {
#ifdef TEST
freopen("zz.in", "r+", stdin);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t; cin >> t;
while (t --) {
int n; cin >> n;
int r0 = n, r1 = n;
int ecnt = 0;
for (int i = 1; i < n; i += 2) {
bool he = qry(i, i+1);
if (he) ++ ecnt, r0 = i, r1 = i+1;
}
if (ecnt > 1) {
cout << "! 1" << '\n';
cout.flush();
continue;
}
if (r0 == r1) {
bool star = qry(r0, 1) && qry(r0, 2) && qry(r0, 3);
cout << (star ? "! 2" : "! 1" ) << '\n';
cout.flush();
} else {
int op1 = r0 == 1 ? 3 : 1;
int op2 = r0 == 1 ? 4 : 2;
bool star = 0;
if (qry(r0, op1)) {
star = qry(r0, op2);
} else {
star = qry(r1, op1) && qry(r1, op2);
}
cout << (star ? "! 2" : "! 1" ) << '\n';
cout.flush();
}
}
}
D. Operator Precedence
D. 題意
構造一個長度為$2n$的序列$a$,使得$\sum_{i=1}^n a_{2i-1}a_{2i}=a_1 a_{2n}\prod_{i=2}^n (a_{2i-2} + a_{2i-1}) \neq 0$。
n ~ 1e5
$1e5$個項連乘可能很大,所以考慮構造成$1$的連乘。最直觀的做法就是把$a[2, 2n-1]$構造成$(-1, 2)$的循環,問題就轉化成求$2a_{2n}-2(n-2)-a_{1}=a_1a_{2n}\neq0$,顯然$(a_1, a_{2n})=(1, 1+2(n-2))$是一個解。
複雜度是 $O(n)$。
D. 代碼
#include "bits/stdc++.h"
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define LNF 0x3f3f3f3f3f3f3f3fll
#define MOD 998244353
int main() {
#ifdef TEST
freopen("zz.in", "r+", stdin);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t; cin >> t;
while (t --) {
int n; cin >> n;
vector<ll> res(2*n);
res[0] = 1;
res[2*n-1] = 1 + 2 * (n - 2);
for (int i = 1; i < 2*n-1; ++ i) res[i] = i%2 == 1 ? -1 : 2;
for (auto x : res) cout << x << ' ';
cout << '\n';
}
}
H. Sugar Sweet II
H. 題意
給定長度為$n$的數組$a$,有$n$個事件對應每個位置:$a_i = a_i + w_i\times (a_i < a_{b_i})$,每個事件發生的順序隨機,求每個$a_i$的期望值。
n ~ 5e5
每個事件可以劃分成三種情況:必然發生$(a_i<a_{b_i}) $,可以發生$(a_{b_i} \leq a_i < a_{b_i} + w_{b_i})$,不會發生$(a_i \geq a_{b_i} + w_{b_i})$。某個位置的期望值就是本身加上位置對應的事件發生的期望值。考慮位置$i$的事件,它的發生必然可以通過一些可以發生的事件回溯到某個必然發生的事件,事件$i$發生等價於這些事件發生的相對順序不變,假設這些事件有$d$個,這些事件順序對總順序的貢獻是$d!$,可得概率$\frac{1}{d!}$。這個回溯的過程可以用多源bfs優化。
複雜度是 $O(n)$。
H. 代碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define INF 0x3f3f3f3f
#define LNF (ll) 0x3f3f3f3f3f3f3f3f
#define MOD (int) (1e9+7)
#define N 500010
ll fac[N], inv[N];
ll fp(ll b, ll p) {
ll c = 1;
for (; p; b=b*b%MOD, p>>=1) if (p&1) c=c*b%MOD;
return c;
}
void init() {
for (int i = fac[0] = 1; i < N; ++ i) fac[i] = i * fac[i-1] % MOD;
}
vector<int> G[N];
ll a[N], b[N], w[N], vis[N], dis[N];
void add_edge(int u, int v) {
G[u].emplace_back(v);
}
int main() {
#ifdef TEST
freopen("zz.in", "r+", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
init();
int t; cin >> t;
while (t --) {
int n; cin >> n;
for (int i = 1; i <= n; ++ i) cin >> a[i];
for (int i = 1; i <= n; ++ i) cin >> b[i];
for (int i = 1; i <= n; ++ i) cin >> w[i];
for (int i = 1; i <= n; ++ i) {
G[i].clear(), vis[i] = 0, dis[i] = 0;
}
queue<int> bfsq;
for (int i = 1; i <= n; ++ i) {
if (a[i] < a[b[i]] + w[b[i]]) {
add_edge(b[i], i);
}
if (a[i] < a[b[i]]) {
vis[i] = 1;
bfsq.push(i);
}
}
int d = 1;
while (!bfsq.empty()) {
int sz = bfsq.size();
while (sz --) {
int u = bfsq.front();
bfsq.pop();
dis[u] = d;
for (auto v : G[u]) {
if (vis[v]) continue;
vis[v] = 1;
bfsq.push(v);
}
}
++ d;
}
for (int i = 1; i <= n; ++ i) {
ll cur = a[i];
if (dis[i] != 0) cur = (cur + w[i] * fp(fac[dis[i]], MOD-2) % MOD) % MOD;
cout << cur << ' ';
}
cout << '\n';
}
}
G. Snake Move
G. 題意
給定一個網格和在上面的一條貪吃蛇,每次操作可以上下左右移動到空的格上,或者縮尾。求蛇頭走到每個格上對應的最小操作數。
n * m ~ 1e5
不考慮蛇尾的話問題等價於最短路。考慮蛇尾,如果位置上是蛇尾,注意到每次操作都會縮尾,所以這個位置的最終代價跟縮尾使得該位置變空的代價取max即可,其他情況等同bfs求最短路。
複雜度是 $O(nm)$。
G. 代碼
#include <bits/stdc++.h>
using namespace std;
#define ll unsigned long long
#define INF 0x3f3f3f3f
#define LNF (ll) 0x3f3f3f3f3f3f3f3f
#define MOD (int) (1e9+7)
#define N 3030
#define M 100010
int dis[N][N], vis[N][N], sg[N][N], g[N][N];
int enq[M];
pair<int, int> snake[M];
int vec[5] = { 0, 1, 0, -1, 0 };
signed main() {
#ifdef TEST
freopen("zz.in", "r+", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m, k; cin >> n >> m >> k;
for (int i = 1; i <= k; ++ i) {
cin >> snake[i].first >> snake[i].second;
sg[snake[i].first][snake[i].second] = i;
}
for (int i = 1; i <= n; ++ i) {
string s; cin >> s;
for (int j = 1; j <= m; ++ j) {
g[i][j] = s[j-1] == '.' ? 1 : 0;
}
}
int d = 0;
queue<pair<int, int>> bfsq;
auto [hx, hy] = snake[1];
bfsq.push(pair<int, int>(hx, hy));
vis[hx][hy] = 1;
dis[hx][hy] = d;
while (!bfsq.empty() || d <= n * m) {
int ck = k - d + 1;
if (ck > 1 && ck <= k && enq[ck]) {
auto [cx, cy] = snake[ck];
bfsq.push(pair<int, int>(cx, cy));
vis[cx][cy] = 1;
dis[cx][cy] = d;
}
int sz = bfsq.size();
++ d;
while (sz --) {
auto [tx, ty] = bfsq.front(); bfsq.pop();
for (int i = 0; i < 4; ++ i) {
int nx = tx + vec[i], ny = ty + vec[i+1];
if (nx < 1 || ny < 1 || nx > n || ny > m) continue;
if (!g[nx][ny]) continue;
if (vis[nx][ny]) continue;
if (sg[nx][ny] != 0 && d < k - sg[nx][ny] + 1) {
enq[sg[nx][ny]] = 1;
continue;
} else if (sg[nx][ny] != 0) {
enq[sg[nx][ny]] = 0;
}
bfsq.push(pair<int, int>(nx, ny));
vis[nx][ny] = 1;
dis[nx][ny] = d;
}
}
}
ll res = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
res += 1ll * dis[i][j] * dis[i][j];
}
}
cout << res << '\n';
}
F. Top Cluster
F. 題意
有一顆$n$個點的點權兩兩不同的樹,有$q$次詢問,每次詢問以$x$為根半徑為$k$的樹的$mex$。
n ~ 5e5
q ~ 5e5
問題等價於整棵樹的$mex$和詢問的樹外的節點的最小值的最小值。考慮求解後者,把節點按點權排序,考慮二分前綴的所有節點是否都在樹中,能夠找到第一個不在樹中的節點,等價於不在樹中且點權最小的節點。判斷前綴是否在樹中等價於判斷點集跟$x$的最遠距離$\leq k$,可以預處理lca和前綴的直徑來快速判斷。
複雜度是 $O((n+q)logn)$。
F. 代碼
#include "bits/stdc++.h"
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define LNF 0x3f3f3f3f3f3f3f3fll
#define MOD 998244353
#define N 500010
#define lg 22
vector<pair<int, int>> G[N];
pair<int, int> ord[N], dia[N];
int st[lg][N<<2], dep[N], seq[N<<2], fr[N], z;
ll dis[N];
void add_edge(int u, int v, int l) {
G[u].emplace_back(v, l);
G[v].emplace_back(u, l);
}
void dfs(int u, int fa, ll pd, int d) {
fr[u] = z;
st[0][z ++] = u;
dis[u] = pd;
dep[u] = d;
for (auto [v, l] : G[u]) {
if (v == fa) continue;
dfs(v, u, pd+l, d+1);
st[0][z ++] = u;
}
}
void init() {
for (int i = 1; i < lg; ++ i) {
for (int j = 0; j + (1 << i) <= z; ++ j) {
int l = j, r = j + (1 << (i-1));
st[i][j] = dep[st[i-1][l]] < dep[st[i-1][r]] ? st[i-1][l] : st[i-1][r];
}
}
}
int lca(int u, int v) {
if (fr[u] > fr[v]) swap(u, v);
int lgn = (int) (log2(fr[v] - fr[u] + 1));
int l = fr[u], r = fr[v] - (1 << lgn) + 1;
return dep[st[lgn][l]] < dep[st[lgn][r]] ? st[lgn][l] : st[lgn][r];
}
ll distance(int u, int v) {
int r = lca(u, v);
return dis[u] + dis[v] - 2ll * dis[r];
}
int main() {
#ifdef TEST
freopen("zz.in", "r+", stdin);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n, q; cin >> n >> q;
int dw = -1;
set<int> wset;
for (int i = 1; i <= n; ++ i) {
int w; cin >> w;
wset.insert(w);
ord[i] = make_pair(w, i);
}
for (int i = 0; i <= n; ++ i) {
if (wset.find(i) == wset.end()) {
dw = i;
break;
}
}
assert(dw != -1);
sort(ord+1, ord+1+n);
dia[1] = make_pair(ord[1].second, ord[1].second);
for (int i = 0; i < n-1; ++ i) {
int u, v, l; cin >> u >> v >> l;
add_edge(u, v, l);
}
dfs(1, 1, 0, 0);
init();
for (int i = 2; i <= n; ++ i) {
auto [pn0, pn1] = dia[i-1];
auto [_, cn] = ord[i];
ll d0 = distance(pn0, pn1);
ll d1 = distance(pn0, cn);
ll d2 = distance(pn1, cn);
dia[i] = dia[i-1];
if (d0 < d1) dia[i] = make_pair(pn0, cn), d0 = d1;
if (d0 < d2) dia[i] = make_pair(pn1, cn), d0 = d2;
}
auto in_range = [&](int r, int x, ll k) {
auto [n0, n1] = dia[r];
return max(distance(n0, x), distance(n1, x)) <= k;
};
while (q --) {
ll x, k; cin >> x >> k;
int l = 1, r = n+1;
while (l < r) {
int m = (l + r) / 2;
if (in_range(m, x, k)) {
l = m+1;
} else {
r = m;
}
}
int res = l == n+1 ? dw : min(dw, ord[l].first);
cout << res << '\n';
}
}