ZJOI2022 部分简要题解
Day 1
树
考虑按照 \(1\sim n\) 的顺序确定每个节点在树上的位置。考虑每个状态只和如下的几个量有关:
- 第一棵树上有多少个节点可以连一个儿子下来
- 第一棵树上有多少个节点必须连一个儿子下来
- 第二棵树现在有多少个联通分量
于是暴力 dp 即可 \(\mathcal O(n^5)\),考虑优化。
考虑前两个条件可以做一个简单容斥干掉:必须连儿子下来我们用可以任意连儿子减去没有连儿子计算。于是只需要记录可以要连儿子的个数,然后转移形如:
然后是第二部分的转移。我们原本的做法是在父亲处统计儿子到它的贡献,我们把这部分贡献转为在儿子上统计,即记录「还有多少个可以连儿子的节点」,不难发现这和前半部分一样可以做容斥干掉,于是复杂度 \(\mathcal O(n^3)\)。
#include <bits/stdc++.h>
using ll = long long;
constexpr int maxn = 505;
struct barrett {
ll M; __int128 mu;
inline void init() { mu = -1ull / M; }
inline ll reduce(ll x) const {
ll res = x - (mu * x >> 64) * M;
return res >= M ? res - M : res;
}
} mod;
inline ll operator%(ll x, const barrett &mod) { return mod.reduce(x); }
inline void operator%=(ll &x, const barrett &mod) { x = mod.reduce(x); }
inline void Inc(ll &x, ll y) { x = x + y >= mod.M ? x + y - mod.M : x + y; }
inline void Dec(ll &x, ll y) { x = x < y ? x - y + mod.M : x - y; }
int n;
ll dp[maxn][maxn][maxn];
int main() {
scanf("%d%lld", &n, &mod.M), mod.init(), --n;
for(int k = 1; k <= n; ++k) dp[1][1][k] = 1;
for(int i = 1; i < n; ++i) {
for(int j = 1; j <= i; ++j) {
for(int k = 1; k <= n - i + 1; ++k) {
ll tmp = j * k * dp[i][j][k] % mod;
Inc(dp[i + 1][j][k - 1], tmp);
Dec(dp[i + 1][j][k], tmp);
Inc(dp[i + 1][j + 1][k], tmp);
Dec(dp[i + 1][j][k], tmp);
}
}
}
for(int i = 1; i <= n; ++i) {
ll ans = 0;
for(int j = 1; j <= i; ++j)
(ans += dp[i][j][1] * j) %= mod;
printf("%lld\n", (ans + mod.M) % mod);
}
}
众数
等价于枚举两个颜色,然后取一段区间,计算第一种颜色在区间外的出现次数和第二种颜色在区间内的出现次数之和。
根号分治。对于出现次数 \(\geq \sqrt n\) 的颜色暴力枚举第二种颜色,然后把它的出现位置提出来 dp,dp 的形式是最大子段和,这部分可以做到单次 \(\mathcal O(n)\)。
剩下的只用考虑两种颜色都是小颜色的贡献。枚举小颜色,然后总共会产生不超过 \(\mathcal O(n\sqrt n)\) 个询问区间众数,且性质是这些询问我们只需要考虑答案 \(\leq \sqrt n\) 的,于是对每个右端点处理出区间众数第一次到达 \(i\) 的左端点,然后依次处理即可。
总复杂度 \(\mathcal O(n\sqrt n)\)
#include <bits/stdc++.h>
constexpr int maxn = 2E+5 + 5;
constexpr int INF = 0x3f3f3f3f;
inline void chmax(int &x, int y) { x < y && (x = y); }
inline void chmin(int &x, int y) { x > y && (x = y); }
int T, n, B, a[maxn], cnt[maxn];
int tot, buc[maxn];
std::vector<int> pos[maxn];
namespace Big {
int f[maxn];
inline void main() {
for(int o = 1; o <= tot; ++o) if(pos[o].size() >= B) {
std::fill(f, f + n + 1, 0);
for(int x : pos[o]) ++f[x];
for(int i = 2; i <= n; ++i) f[i] += f[i - 1];
for(int i = 1; i <= tot; ++i) {
if(pos[i].size() > pos[o].size()) continue;
if(pos[i].size() == pos[o].size() && i < o) continue;
int mn1 = 0, mn2 = 0, MX1 = -INF, MX2 = -INF;
int cur = 0;
#define upd(p)\
do {\
if(p > n) break;\
\
chmax(MX1, f[p] - cur - mn1);\
chmax(MX2, cur - f[p] - mn2);\
chmin(mn1, f[p] - cur);\
chmin(mn2, cur - f[p]);\
} while(false)
for(int j = 0; j < pos[i].size(); ++j) {
int p = pos[i][j];
upd(p - 1);
++cur;
upd(p);
if(j == pos[i].size() - 1 || pos[i][j + 1] != p + 1) upd(p + 1);
}
chmax(cnt[i], pos[i].size() + MX1);
chmax(cnt[o], pos[o].size() + MX2);
}
}
}
}
namespace Small {
int id[maxn], lpos[maxn][305];
int tmp[maxn], tim[maxn];
inline void main() {
for(int i = 1; i <= tot; ++i)
for(int j = 0; j < pos[i].size(); ++j) id[pos[i][j]] = j;
for(int i = 1; i <= B; ++i) {
std::fill(tmp, tmp + tot + 1, 0);
std::fill(tim, tim + n + 1, 0), tim[0] = tot;
int mx = 0;
for(int r = 1, l = 1; r <= n; ++r) {
--tim[tmp[a[r]]], ++tim[++tmp[a[r]]];
chmax(mx, tmp[a[r]]);
while(l <= r && mx >= i) {
--tim[tmp[a[l]]], ++tim[--tmp[a[l]]];
if(!tim[mx]) --mx;
++l;
}
lpos[r][i] = l - 1;
}
}
for(int r = 1; r < n; ++r) {
int p = B, x = a[r + 1];
for(int l = 0; l <= pos[x].size(); ++l) {
int L = !l ? 1 : pos[x][l - 1] + 1;
if(L <= r) {
while(lpos[r][p] < L) --p;
chmax(cnt[x], p + (pos[x].size() - id[r + 1] + l));
}
else break;
}
}
int p = B;
for(int l = 2; l <= n; ++l) {
while(lpos[n][p] < l) --p;
chmax(cnt[a[l - 1]], id[l - 1] + 1 + p);
}
}
}
int main() {
std::cin.tie(0)->sync_with_stdio(false);
std::cin >> T;
while(T --> 0) {
std::cin >> n, tot = 0;
for(int i = 1; i <= n; ++i)
std::cin >> a[i], buc[++tot] = a[i];
B = 300;
fprintf(stderr, "%d\n", B);
std::sort(buc + 1, buc + tot + 1);
tot = std::unique(buc + 1, buc + tot + 1) - buc - 1;
for(int i = 1; i <= tot; ++i) pos[i].clear();
for(int i = 1; i <= n; ++i) {
a[i] = std::lower_bound(buc + 1, buc + tot + 1, a[i]) - buc;
pos[a[i]].push_back(i);
}
for(int i = 1; i <= tot; ++i) cnt[i] = pos[i].size();
int t = 0;
for(int i = 1; i <= tot; ++i)
if(pos[i].size() < B) t = std::max(t, (int)pos[i].size());
B = t + 1;
fprintf(stderr, "%d\n", B);
Big::main(), Small::main();
int ans = *std::max_element(cnt + 1, cnt + tot + 1);
std::cout << ans << "\n";
for(int i = 1; i <= tot; ++i)
if(cnt[i] == ans) std::cout << buc[i] << "\n";
}
}
简单题
观察一下发现如果有三个简单环 \(x,y,z\),且 \(x\) 和 \(y,z\) 都有交,那么一定满足 \(x\cap y=x\cap z\)。于是可以断言:对于每个点双都存在两个点,使得这个点双的结构是两个点和其间的若干不相交简单路径。
那么求两个在同一个点双里的点的答案是充分好做的,那么建立圆方树,类似仙人掌最短路做即可。
#include <bits/stdc++.h>
using ll = long long;
constexpr ll mod = 998244353;
constexpr int maxn = 1E+6 + 5;
inline ll fsp(ll a, ll b, ll res = 1) {
for(a %= mod; b; a = a * a % mod, b >>= 1)
b & 1 && (res = res * a % mod); return res;
}
int n, cnt, q;
struct Value {
ll a, b;
inline Value(ll _a = 0, ll _b = 0) : a(_a), b(_b) {
assert(a > -mod && a < mod);
assert(b > -mod && b < mod);
}
inline Value operator+(const Value &rhs) const {
return Value((a * rhs.b + b * rhs.a) % mod, b * rhs.b % mod);
}
inline Value operator-(const Value &rhs) const {
Value res;
ll invb = fsp(rhs.b, mod - 2);
res.b = b * invb % mod;
res.a = (a - res.b * rhs.a) % mod * invb % mod;
return res;
}
} S[maxn];
std::vector<std::pair<int, Value>> to[maxn];
namespace Fuck {
struct Shit {
std::vector<int> vec;
int x, p, q, *id, *pos; ll *w[2], sum;
inline void init();
inline Value query(int s, int t) {
if(s == t) return Value(0, 1);
s = std::lower_bound(vec.begin(), vec.end(), s) - vec.begin();
t = std::lower_bound(vec.begin(), vec.end(), t) - vec.begin();
if((s == p || s == q) && (t == p || t == q)) return Value(sum, x);
if((s == p || s == q) || (t == p || t == q)) {
if(t == p || t == q) std::swap(s, t);
if(s == p) return Value((sum + (x - 2) * w[1][t]) % mod, x);
else return Value((sum + (x - 2) * w[0][t]) % mod, x);
}
if(id[s] != id[t])
return Value((2 * sum + (x - 3) * (w[0][s] + w[0][t] + w[1][s] + w[1][t])) % mod, x * 2 - 2);
else {
if(pos[s] < pos[t]) std::swap(s, t);
ll v = (w[0][s] + w[1][t]) % mod;
return Value((sum + (x - 2) * v) % mod, x);
}
}
} dcc[maxn];
int rid[maxn];
std::vector<std::array<int, 3>> edge;
std::vector<std::pair<int, ll>> to[maxn];
inline void DFS(int u, int t, int fa, int *id, int *pos, ll cur, ll *w) {
if(id[u] != -1) {
if(id[fa] != -1) id[u] = id[fa], pos[u] = pos[fa] + 1;
else id[u] = u, pos[u] = 1;
}
w[u] = cur;
if(u == t) return;
for(auto e : to[u]) if(e.first != fa)
DFS(e.first, t, u, id, pos, (cur + e.second) % mod, w);
}
inline void Shit::init() {
std::sort(vec.begin(), vec.end());
w[0] = new ll[vec.size()], w[1] = new ll[vec.size()];
id = new int[vec.size()], pos = new int[vec.size()];
for(int i = 0; i < vec.size(); ++i) rid[vec[i]] = i, to[i].clear();
for(auto e : edge) {
to[rid[e[0]]].emplace_back(rid[e[1]], e[2]);
to[rid[e[1]]].emplace_back(rid[e[0]], e[2]);
(sum += e[2]) %= mod;
}
p = -1, q = -1;
for(int i = 0; i < vec.size(); ++i) {
if(!~p || to[i].size() > to[p].size()) q = p, p = i;
else if(!~q || to[i].size() > to[q].size()) q = i;
}
id[p] = id[q] = -1, x = to[p].size();
DFS(p, q, -1, id, pos, 0, w[0]), DFS(q, p, -1, id, pos, 0, w[1]);
}
}
Fuck::Shit dcc[maxn];
namespace prework {
int m; std::vector<std::pair<int, int>> to[maxn];
int ind, dfn[maxn], low[maxn];
int top1, sta1[maxn];
int top2; std::array<int, 3> sta2[maxn];
inline void Tarjan(int u) {
low[u] = dfn[u] = ++ind, sta1[++top1] = u;
for(auto e : to[u]) {
int v = e.first;
if(!dfn[v]) {
int top0 = top2;
Tarjan(v), low[u] = std::min(low[u], low[v]);
if(low[v] >= dfn[u]) {
++cnt, dcc[cnt].vec.push_back(u);
do dcc[cnt].vec.push_back(sta1[top1]); while(sta1[top1--] != v);
while(top2 > top0) Fuck::edge.push_back(sta2[top2--]);
dcc[cnt].init(), Fuck::edge.clear();
for(int x : dcc[cnt].vec) {
Value w = dcc[cnt].query(x, u);
::to[x].emplace_back(n + cnt, w);
::to[n + cnt].emplace_back(x, w);
}
}
} else {
low[u] = std::min(low[u], dfn[v]);
if(dfn[v] < dfn[u]) sta2[++top2] = { u, v, (int)e.second };
}
}
}
inline void main() {
scanf("%d%d%d", &n, &m, &q);
for(int i = 1, u, v, w; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
to[u].emplace_back(v, w);
to[v].emplace_back(u, w);
}
Tarjan(1);
}
}
int ind, dfn[maxn], dep[maxn];
int ddfn[maxn], jp[maxn][25];
std::vector<int> son[maxn];
inline void DFS(int u, int fa) {
dfn[u] = ++ind, dep[u] = dep[fa] + 1;
jp[u][0] = fa, ddfn[ind] = u;
for(int i = 1; i <= 20; ++i)
jp[u][i] = jp[jp[u][i - 1]][i - 1];
if(!fa) S[u] = Value(0, 1);
for(auto e : to[u]) if(e.first != fa) {
S[e.first] = S[u] + e.second, DFS(e.first, u);
son[u].push_back(dfn[e.first]);
}
}
inline int LCA(int x, int y) {
if(dep[x] < dep[y]) std::swap(x, y);
for(int i = 20; i >= 0; --i)
if(dep[jp[x][i]] >= dep[y]) x = jp[x][i];
if(x == y) return x;
for(int i = 20; i >= 0; --i)
if(jp[x][i] != jp[y][i]) x = jp[x][i], y = jp[y][i];
return jp[x][0];
}
inline int getp(int x, int g) {
return ddfn[*(std::upper_bound(son[g].begin(), son[g].end(), dfn[x]) - 1)];
}
int main() {
prework::main(), DFS(1, 0);
for(int x, y; q --> 0;) {
scanf("%d%d", &x, &y);
int g = LCA(x, y);
if(g > n) {
int px = getp(x, g), py = getp(y, g);
Value ans = S[x] + S[y] - (S[px] + S[py]);
ans = ans + dcc[g - n].query(px, py);
printf("%lld\n", (ans.a + mod) % mod);
} else printf("%lld\n", ((S[x] + S[y] - (S[g] + S[g])).a + mod) % mod);
}
}
Day 2
面条
先不考虑每轮的除以 \(2\) 的操作。
记 \(B=\max_{2^x|n}x\),暴力做前 \(B+1\) 轮,然后之后每一轮的序列都长成 aaaabbbbccccddddeeeeff
的样子。也即,前面是若干个重复出现 \(2^{B+1}\) 的段,最后一段是长为 \(2^B\) 的。
考虑做一轮操作会变成:
其差分序列是
假设最初的差分是 \(d_1,d_2,\cdots,d_n\),那么每一轮会变成 \(-d_n,d_1,-d_{n-1},d_2,\cdots\)。构造序列
于是每一轮的操作实际上是对整个序列的置换,于是只需要求 \(a_0\) 即可得到 \(a_x\)。注意到每轮操作是不改变总和的,于是可以用 \(d_1',d_2',\cdots,d_n'\) 表示出 \(a_x\)。
记置换中的一个环每个位置的值是 \(w_i\),系数是 \(c_i\),那么要求的无非是
这是一个卷积,预处理出每个环的答案,然后把长度相等的环合并即可做到 \(\mathcal O(q\sqrt n)\)。
然后注意到我们的置换的实质是 \(d_{2i\bmod{2n+1}}'\gets d_{i}\),于是这个置换的循环节不会超过 \(\varphi(2n+1)\),于是所有环长的 \(\rm{lcm}\) 是充分小的,直接计算 \(k\bmod \rm{lcm}\) 处的值即可。
复杂度 \(\mathcal O(n\log n+nd(n)+q)\)
#include "poly.h"
namespace FuckZJOI {
using ull = unsigned long long;
ull seed;
inline ull rd(ull &x = seed) {
x ^= (x << 13);
x ^= (x >> 7);
x ^= (x << 17);
return x;
}
}
using FuckZJOI::rd;
constexpr int maxn = 4E+6 + 5;
int n, T, q, x;
ll kmax, a[25][maxn], ans0[25], d[maxn];
template<const int B>
struct Lpow {
ll pw1[B + 5], pw2[B + 5];
inline constexpr Lpow(ll x) {
pw1[0] = 1;
for(int i = 1; i <= B; ++i) pw1[i] = pw1[i - 1] * x % mod;
pw2[0] = 1, x = pw1[B];
for(int i = 1; i <= B; ++i) pw2[i] = pw2[i - 1] * x % mod;
}
inline ll lp(ll x) { x %= mod - 1; return pw1[x % B] * pw2[x / B] % mod; }
};
Lpow<32768> L1(2), L2(inv2);
int B, r; ll S, invn, fval[maxn];
std::vector<int> siz;
void init() {
ans0[0] = a[0][x], invn = fsp(n, mod - 2);
B = std::__lg(n & -n), S = 0;
for(int i = 1; i <= B + 1; ++i) {
for(int j = 1; j <= n; ++j)
a[i][j] = (a[i - 1][j + 1 >> 1] + a[i - 1][n - (j + 1 >> 1) + 1]) % mod;
ans0[i] = a[i][x];
}
for(int i = 1; i <= n; ++i) (S += a[B + 1][i]) %= mod;
x = (x - 1 >> B + 1) + 1, n = ((n >> B) + 1 >> 1) - 1;
for(int i = 1; i <= n; ++i)
d[i] = (a[B + 1][(i << B + 1) + 1] - a[B + 1][i << B + 1] + mod) % mod;
static ll coef[maxn];
for(int i = 1; i <= n; ++i) coef[i] = mod - ((n - i + 1) * 2 - 1 << B);
for(int i = 1; i <= n; ++i) coef[i] = coef[i] * invn % mod;
for(int i = 2; i <= x; ++i) ++coef[i - 1];
static int to[maxn];
auto Add = [&](int x, int y) { to[y] = x; };
for(int l = 1, r = n, i = 1; i <= n; ++i) {
if(i & 1) Add(i, r + n), Add(i + n, r), --r;
else Add(i, l), Add(i + n, l + n), ++l;
}
static bool vis[maxn];
std::fill(vis, vis + n * 2 + 1, 0);
std::vector<int> cur;
std::function<void(int)> DFS = [&](int x) {
if(vis[x]) return;
vis[x] = 1, cur.push_back(x), DFS(to[x]);
};
r = 1;
for(int i = 1; i <= n * 2; ++i) if(!vis[i])
cur.clear(), DFS(i), r = r / std::__gcd(r, (int)cur.size()) * cur.size();
std::fill(vis, vis + n * 2 + 1, 0);
for(int i = 0; i < r; ++i) fval[i] = 0;
for(int i = 1; i <= n * 2; ++i) if(!vis[i]) {
cur.clear(), DFS(i), siz.push_back(cur.size());
poly F, G;
F.redeg(cur.size() - 1), G.redeg(cur.size() - 1);
for(int i = 0; i < cur.size(); ++i) {
F[i] = cur[i] <= n ? coef[cur[i]] : 0;
G[i] = cur[i] <= n ? d[cur[i]] : (mod - d[cur[i] - n]) % mod;
}
std::reverse(G.f.begin(), G.f.end());
G = (G << cur.size()) + G;
F = F * G >> cur.size() - 1;
for(int j = 0; j < r; ++j) (fval[j] += F[j % cur.size()]) %= mod;
}
}
inline ll calc(ll k) {
if(k <= B + 1) return ans0[k];
k -= B + 1;
return (S * invn % mod * L1.lp(k) + fval[k % r]) % mod;
}
int main() {
scanf("%*d%d%llu", &T, &FuckZJOI::seed);
while(T --> 0) {
scanf("%d%d%d%lld", &n, &q, &x, &kmax);
for(int i = 1; i <= n; ++i) scanf("%lld", &a[0][i]);
init();
ll ans = 0;
for(ll k, i = 1; i <= q; ++i)
k = rd() % kmax, ans ^= calc(k) * L2.lp(k) % mod * i;
printf("%lld\n", ans);
}
}
计算几何
考虑每个点恰好和两个被 ban 掉的点相邻,于是一个简单的想法是把这些点看成被 ban 掉的点之间的连边,于是问题变成了最大匹配的大小,这就是 LOJ6677。
#include <bits/stdc++.h>
using ll = long long;
constexpr ll mod = 998244353;
constexpr int maxn = 3E+6 + 5;
inline ll fsp(ll a, ll b, ll res = 1) {
for(a %= mod; b; a = a * a % mod, b >>= 1)
b & 1 && (res = res * a % mod); return res;
}
int T, N; ll a, b, c;
ll Fac[maxn], H[maxn];
inline ll calc(int a, int b, int c) {
if(N < a + b + c) {
int T = a + b + c;
for(int i = N + 1; i <= T; ++i) Fac[i] = Fac[i - 1] * i % mod;
for(int i = N + 1; i <= T; ++i) H[i] = H[i - 1] * Fac[i - 1] % mod;
N = T;
}
return H[a] * H[b] % mod * H[c] % mod * H[a + b + c] % mod
* fsp(H[a + b] * H[a + c] % mod * H[b + c], mod - 2) % mod;
}
int main() {
N = 0, Fac[0] = H[0] = 1;
scanf("%d", &T);
while(T --> 0) {
scanf("%lld%lld%lld", &a, &b, &c);
ll &x = const_cast<ll&>(std::max(a, std::max(b, c)));
x = std::min(x, a + b + c - x);
ll ans1 = (a + b + c) * (a + b + c) - 2 * (a * a + b * b + c * c);
printf("%lld %lld\n", ans1, calc(a + b - c, b + c - a, c + a - b));
}
}