2025.8.25模拟赛
T1
形式化题面:初始有一张 \(n\) 个点的完全图,接着删除 \(m\) 条边。询问有多少长度为 \(13\) 的序列 \(p_1,\dots,p_{13}\) 满足:
\(1\le p_i\le n\)。
\((p_1,p_2),(p_2,p_3),(p_1,p_4),(p_2,p_5)\) 有连边。
\((p_6,p_7),(p_7,p_8),(p_8,p_9)\) 有连边。
\((p_{10},p_{11}),(p_{11},p_{12}),(p_{12},p_{13})\) 有连边。
对 \(998244353\) 取模。
\(1\le n,m\le 10^5\)。
其实这形成了一个 FCC 形状(部分序列)。
\(f_i\) 表示 \(i\) 的度数,也就是通向 \(i\) 长度为 \(1\) 的路径的个数。\(g_i\) 为通向 \(i\) 长度为 \(2\) 的路径的个数。
正着算比较难。直接倒着减。发现完全图两步就能到达所有边,所以 \(g_i=sum-\sum_{u\not \to v} f_v\),\(sum\) 为所有点度数和,\(u\not \to v\) 表示 \(u\) 与 \(v\) 无连边,其中包括 \(u\) 本身。
枚举 \(p_2\) 计算 F,枚举 \(p_7\) 计算 C。F 答案为 \(\sum_{i=1}^n f_if_ig_i\),C 答案为 \(\sum_{i=1}^n f_ig_i\)。
然后乘起来就行了。
赛时代码
#include <bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N = 1e5 + 5, P = 998244353;
int n, m, f[N], g[N];
int ans1, ans2, sum;
vector<int> e[N];
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i)
f[i] = n - 1;
for (int i = 1, u, v; i <= m; ++i)
cin >> u >> v, --f[u], --f[v], e[u].eb(v), e[v].eb(u);
for (int u = 1; u <= n; ++u) {
add(sum, f[u]);
for (int v : e[u])
add(g[u], P - f[v]);
}
for (int i = 1; i <= n; ++i) {
add(g[i], sum), add(g[i], P - f[i]);
add(ans1, 1ll * f[i] * g[i] % P);
add(ans2, 1ll * f[i] * f[i] % P * g[i] % P);
}
cout << 1ll * ans1 * ans1 % P * ans2 % P << '\n';
return 0;
}
T2
初始有一棵 \(n\) 个点的树,标号为 \(1\sim n\),接下来有 \(m\) 次操作,第 \(i\) 次操作(\(1\le i\le m\))会给出 \(u_i,v_i\),把这条路径上的点合并成一个新的点 \(n+i\),这些点会在树上消失,这些点的出边的并集删去不存在的点就是 \(n+i\) 的连边(保证操作前 \(u_i,v_i\) 仍然在树上)。
你需要在每次操作后算出树上有多少个点。
\(1\le n,m\le 5\times 10^5\)。
每次合并的复杂度为合并部分大小的复杂度是可以接受的。
每次将这条路径抠出来,并查集中都连向 \(n+i\)。下次遇到 \(x\) 点直接访问 \(x\) 的至高无上祖先即可。
一个问题是扣路径不知道点的深度,所以 考虑 \(x,y\) 一起向上爬,如果那个人走到了之前有人走过的位置哪就是最近公共祖先。
赛时代码
#include <bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N = 1e6 + 5;
int n, m, ans;
int fa[N], ga[N], vis[N];
vector<int> e[N];
void dfs(int u) {
for (int v : e[u])
if (v != ga[u])
ga[v] = u, dfs(v);
}
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int upd(int x, int y, int t) {
int u = x, v = y, p = 0, res = 0;
while (1) {
if (vis[x] == t && x != 0) {
p = x;
break;
}
vis[x] = t, x = find(ga[x]), swap(x, y);
}
while (u != p)
++res, fa[u] = t, u = find(ga[u]);
while (v != p)
++res, fa[v] = t, v = find(ga[v]);
ga[t] = ga[p], fa[p] = t;
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1, u, v; i < n; ++i)
cin >> u >> v, e[u].eb(v), e[v].eb(u);
for (int i = 1; i <= n + m; ++i)
fa[i] = i;
dfs(1), ans = n;
for (int t = 1, x, y; t <= m; ++t)
cin >> x >> y, ans -= upd(x, y, n + t), cout << ans << '\n';
return 0;
}
T3
平面上有 \(n\) 个点,你要找到一个最小的 \(r\) 使得每个点为圆心画半径为 \(r\) 的圆,存在一条直线经过所有圆(相切也算经过)。
\(1\le 10^6\le n,1\le x_i,y_i\le 10^9\)。
答案输出一个有理数 \(r^2\),即a/b的形式。
只用考虑凸包上的点是肯定的。
赛时由于没有性质完全不可做,所以我们强制一些性质:这条直线一定与凸包的边平行。
点击查看证明
考虑距离直线最远的点的数量,若只有一个的话一定可以将直线向这个点移动一些以减小最大半径。
对于大于等于两个的情况,如果存在两个在同侧,那就是与一条边平行,将这条直线向两个同侧点移动直到同时另一侧有点距离最远。否则就是只有两个且在异侧,那我们稍微旋转这条直线就可以使两者距离都减小。
也就是只有存在两个都在同侧的情况是呆得住的,其余都可以微调获得更优答案。
然后相当于枚举边,求距离这条边所在直线最远的点,就获得了 \(O(n^2)\) 的做法。
涉及的计算几何知识
直线方程式 \(Ax+By+C=0\),过 \((x_1,y_1),(x_2,y_2)\) 的直线为 \((y_2-y_1)x+(x_1-x_2)y+x_2y_1-x_1y_2=0\)。
点 \((x',y')\) 与直线 \(Ax+By+C=0\) 的距离为 \(\frac{|Ax'+By'+C|}{\sqrt{A^2+B^2}}\),实际运算时取平方 \(\frac{(Ax'+By'+C)^2}{A^2+B^2}\) 即可。
然后会发现这条边绕着凸包旋转时,那个点也跟着旋转,然后记录点的位置,跟着边旋转就可获得 \(O(n\log n)\) 的做法(疑似旋转卡壳),瓶颈是找凸包的复杂度。
答案上界在 __int128 范围内,但是比较两个有理数大小是直接将其转为 long double 计算,不然会爆。
代码实现有一堆重定义运算。
赛时代码
#include <bits/stdc++.h>
#define Int __int128
#define int long long
#define db double
#define ldb long double
using namespace std;
const int N = 2e6 + 5, P = 998244353;
Int gcd(Int x, Int y) { return (y == 0 ? x : gcd(y, x % y)); }
Int abs(Int x) { return x > 0 ? x : -x; }
void write(Int x) {
if (x == 0)
return;
write(x / 10), putchar(x % 10 + '0');
}
struct cop {
Int gty, zyq;
cop fix() {
if (gty == 0)
return zyq = 1, *this;
int d = gcd(gty, zyq);
gty /= d, zyq /= d;
if (zyq < 0)
gty = -gty, zyq = -zyq;
return *this;
}
void init(int x) { gty = x, zyq = 1; }
void init(int x, int y) { gty = x, zyq = y, fix(); }
} ans;
cop operator+(const cop &x, const cop &y) { return (cop){x.gty * y.zyq + x.zyq * y.gty, x.zyq * y.zyq}.fix(); }
cop operator-(const cop &x, const cop &y) { return (cop){x.gty * y.zyq - y.gty * x.zyq, x.zyq * y.zyq}.fix(); }
cop operator*(const cop &x, const cop &y) { return (cop){x.gty * y.gty, x.zyq * y.zyq}.fix(); }
cop operator/(const cop &x, const cop &y) { return (cop){x.gty * y.zyq, x.zyq * y.gty}.fix(); }
cop operator*(const cop &x, const int &y) { return (cop){x.gty * y, x.zyq}.fix(); }
cop operator/(const cop &x, const int &y) { return (cop){x.gty, x.zyq * y}.fix(); }
bool operator<(const cop &x, const cop &y) {
ldb a = x.gty, b = x.zyq, c = y.gty, d = y.zyq;
return a / b < c / d;
}
struct line {
cop A, B, C;
void init(int x1, int y1, int x2, int y2) {
A.init(y2 - y1), B.init(x1 - x2);
C.init(x2 * y1 - x1 * y2);
}
cop dis(int x, int y) { return (A * x + B * y + C) * (A * x + B * y + C) / (A * A + B * B); }
} L;
int n, top, pot, s[N];
struct node {
db x, y;
} a[N];
bool cmp(const node &x, const node &y) { return x.x != y.x ? x.x < y.x : x.y < y.y; }
db dis(const node &a, const node &b) { return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); }
db get(const node &a, const node &b) { return a.x * b.y - a.y * b.x; }
db get(const node &a, const node &b, const node &c) { return get((node){b.x - a.x, b.y - a.y}, (node){c.x - a.x, c.y - a.y}); }
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
if (n <= 2)
return cout << "0/1" << '\n', 0;
for (int i = 1; i <= n; ++i)
cin >> a[i].x >> a[i].y;
sort(a + 1, a + n + 1, cmp);
s[0] = 1, s[top = 1] = 2;
for (int i = 3; i <= n; ++i) {
while (top && get(a[s[top - 1]], a[s[top]], a[i]) <= 0)
--top;
s[++top] = i;
}
pot = top, s[++top] = n - 1;
for (int i = n - 2; i >= 1; --i) {
while (top > pot && get(a[s[top - 1]], a[s[top]], a[i]) <= 0)
--top;
s[++top] = i;
}
ans.init(1e18);
for (int i = 1, p = 3; i < top; ++i) {
L.init(a[s[i]].x, a[s[i]].y, a[s[i + 1]].x, a[s[i + 1]].y);
cop maxdis;
maxdis = L.dis(a[s[p]].x, a[s[p]].y);
while (maxdis < L.dis(a[s[p % top + 1]].x, a[s[p % top + 1]].y))
p = p % top + 1, maxdis = L.dis(a[s[p]].x, a[s[p]].y);
ans = min(ans, maxdis);
}
ans = ans / 4;
write(ans.gty), putchar('/');
write(ans.zyq), putchar('\n');
return 0;
}
T4
复杂的题面不想转了。
总之一堆 tag,线段树/分块,然后合并一堆信息,全员代码 5k+,写了有害身心健康。

浙公网安备 33010602011771号