Day 1

Day 1

我的大脑好像被鸣笛的火车穿过一样,冒出令人热的发呕的黑烟。

入营测试

测试前我们误打误撞的进入了不属于我们的屋子。并不想提起。

随即进入了正确的位置,一行人四散开来流落四方。然而因为各种原因没能及时进行答题——耽搁了四十分钟吧。

user_is_banned

T1

简单来说就是给你一个排列让你选出 \(k\) 个数字令它们的数值连续. 求最小下标覆盖区间.

既然是连续的我们就想到线段树, 然后就想到了区间查极值.

我们开一个值域线段树, 每次选取一个长度为 \(k\) 的区间进行查极差, 然后记录最小极差.

赛后想到了我们可以用单调队列做到 \(\mathcal{O}(n)\) 且是滑动窗口板子. 不过 \(\mathcal{O}(n\log n)\) 能过就是了.

#include <bits/stdc++.h>
using namespace std;

namespace OI {

#define int long long
#define endl "\n"

/*

当我们依次选取一个数字作为最小值的时候,我们有 n - k + 1 个序列
然后我们查询这 n - k + 1 个数字所在的位置

*/

constexpr int maxn = 2e5+5;

int n, k;
int a[maxn];

struct node {
	int min, max;
} seg[maxn << 2];

void pushUp(int x) {
	seg[x].min = min(seg[x << 1].min, seg[x << 1 | 1].min),
	seg[x].max = max(seg[x << 1].max, seg[x << 1 | 1].max);
}

void build(int x,int L, int R) {
	if (L == R) {
		seg[x].min = seg[x].max = a[L];
		return;
	}
	int mid = L + R >> 1;
	build(x << 1, L, mid),
	build(x << 1 | 1, mid + 1, R);
	pushUp(x);
}

int queryMin(int x, int L, int R, int l, int r) {
	if (l <= L && R <= r)
		return seg[x].min;
	int mid = L + R >> 1;
	int ans = INT_MAX;
	if (l <= mid)
		ans = min(ans, queryMin(x << 1, L, mid, l, r));
	if (mid < r)
		ans = min(ans, queryMin(x << 1 | 1, mid + 1, R, l, r));
	return ans;
}

int queryMax(int x, int L, int R, int l, int r) {
	if (l <= L && R <= r)
		return seg[x].max;
	int mid = L + R >> 1;
	int ans = 0;
	if (l <= mid)
		ans = max(ans, queryMax(x << 1, L, mid, l, r));
	if (mid < r)
		ans = max(ans, queryMax(x << 1 | 1, mid + 1, R, l, r));
	return ans;
}

void main() {
	cin >> n >> k;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		a[x] = i;
	}
	build(1, 1, n);
	int ans = INT_MAX;
	for (int i = 1; i + k - 1 <= n; i++)
		ans = min(ans, queryMax(1, 1, n, i, i + k - 1) - queryMin(1, 1, n, i, i + k - 1));
	cout << ans << endl;
}

#undef int
#undef endl

}

int main() {
	freopen("span.in", "r", stdin);
	freopen("span.out", "w", stdout);

	cin.tie(0), cout.tie(0);
	ios::sync_with_stdio(0);
	
	OI::main();
	
	return 0;
}

大概是因为热的吧, 此刻我的皮肤又有些作痛了.

T2

给你一个图和 \(q\) 次操作, 每次选择一个点并将其相邻的节点染色成与本节点相同. 初始颜色各不相同.

赛时的想法就是暴力(因为暴力有 \(60pts\)), 想到了一堆错误的解法然后一一否决.

赛后告诉我是:

\[\LARGE \textcolor{red}{炫} \textcolor{orange}{酷} \textcolor{yellow}{的} \textcolor{green}{根} \textcolor{cyan}{号} \textcolor{blue}{分} \textcolor{purple}{治} \]

简而言之有两种做法, 给每个点进行暴力操作是 \(\mathcal{O}(m)\)\(\mathcal{O}(1)\) 查, 而给每个点打 \(tag\)\(\mathcal{O}(1)\)\(\mathcal{O}(m)\) 查.

使用根号分治折中, 最终复杂度是 \(\mathcal{O}(q\sqrt m)\) .

T3

题意是给你一个集合, 让你选定一个 \(p\) 让这个集合在模 \(p\) 的意义下众数量最大.

考试的时候想到了第一个性质因为很显然用质数作为模数是很好的. 拿了 \(40pts\) 暴力. 剩下拼的模拟退火一分没拿.

赛时我对 lzy 说了一句很有意义的话:

时限是两秒,绝对是随机化!

没什么道理,但是是伏笔.

赛后告诉我还有一个性质是因为这个质数它的取值对应的贡献一定是比众数大的.

显然可得 \(p \le \frac{2V}{n - 2}\)

好的这样你有 \(70pts\) 了.

剩下的 \(30pts\) 是每次随机选取两个元素并把它们的差质因数分解并计算每个质因数的贡献, 然后通过一系列证明可以得到你进行大概 \(30\) 次就可让正确率不低于 \(99.9\%\) .

我的天呐.

T4

你一开始有一个点, \(m\) 次操作, 每次可以选取两个以前的可重复的两棵树并连接成新的树. 求这树的两两节点距离和.

赛时时间不够了(因为我和 lzy 因为各种原因少了半个小时答题时间. 我没招了.)

考虑从增量的角度计算答案, 若我们在构造树 \(T_n\) 时已经得到了树 \(T_x\) 和树 \(T_y\) 的答
案.

考虑 \(Ans_n\)\(Ans_x + Ans_y\), 发现即为跨越新增的边的所有路径权值之和。其可以表
示为:

\[f(x, u) \times size_y + f(y, v) \times size_x + w * size_x * size_y \]

其中 \(f(n, p)\) 表示 \(\sum_{i \in [0, n)} dist(p, i)\), 不妨通过递归求出,考虑 \(p\) 在哪棵树中,那么另一棵子树的贡献可以拆分为
连接点到其所有节点的距离之和再加上连接点到 \(p\) 的距离即可,不妨设 \(p\)\(T_x\) 中, 那
么我们有:

\[f(n, p) = f(x, p) + f(y, v) + dist_n(x, v) \times size_y \]

复杂度大概是 \(\mathcal{O}(n^3)\) .

午休

午饭我吃的比较晚. 因为外卖员耽搁了一些时间, 送餐机器人又耽搁了一些时间.

lzy 的腿不知为何在作痛. 但愿世上不要再有病痛了.

午休结束时我走的楼梯, 然后发现楼梯间门是锁的, 最后被迫走回六楼再坐电梯. 出门后又迷路了.

Damn it.

好在只是迟到了 \(3mins\) 约等于没迟到.

下午

注意到 \(OJ\) 的近日运势告诉我忌学数论. 可今天是数论专题.

T1

莫比乌斯反演我不会, 先看 T2 了.

T2

这个构造已经超越人类范畴了.

你有一个数字集, 一开始有一个奇数, 每次可以选取其中的两个数(可相同)进行加法或异或并加入新的数字. 消成 \(1\) 的构造.

我们一开始有形如 \(1?1\) 的数字. \(?\) 是一个 \(01\) 串.

我们每次要对现在的 \(01\) 串消去最高位的 \(1\).

首先我们要让它左移 \(\lfloor\log_2x\rfloor\) 位让它变成:

00001???1
1???10000

这样.

显然这两个数字异或后可以得到:

1???0???1

且这个数字和第二个数字相加可以得到:

1???01???1

我们让第二个数字左移一位, 与我们刚才得到的数字和初始数字依次异或就可以得到:

10000

这是一个 \(2\) 的整数次幂. 我们可以利用它消去第三个数字的所有 \(1\) 最终得到 \(???1\) . 然后进行下一次轮回.

#include <bits/stdc++.h>
using namespace std;

namespace OI {

#define int long long
#define endl "\n"

int x;

struct opt {
    int a, b, t;
};
vector<opt> out;

int add(int a, int b) {
    out.push_back({a, b, 0});
    return a + b;
}

int XOR(int a, int b) {
    out.push_back({a, b, 1});
    return a ^ b;
}

void main() {
    cin >> x;
    while (x != 1) {
        int sec = x;
        while (sec < (x << __lg(x)))
            sec = add(sec, sec);
        int trd = XOR(x, sec);
        int fth = add(sec, trd);
        int fif = add(sec, sec);
        int sxt = XOR(x, fth);
        int tag = XOR(fif, sxt);
        x = trd;
        while (tag < x) {
            if (x > (tag ^ x))
                x = XOR(tag, x);
            if (tag < x)
                tag = add(tag, tag);
        }
    }
    cout << out.size() << endl;
    for (auto o : out) {
        if (o.t == 0)
            cout << o.a << " + " << o.b << endl;
        else
            cout << o.a << " ^ " << o.b << endl;
    }
}

#undef int
#undef endl

}

int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);

    OI::main();

    return 0;
}

下午休息

大家都不怎么饿,也便没有吃饭。

去地下室看了眼钢琴。地下室的氛围有些诡异啊,看起来像 Backrooms.

晚自习

T1

回过头看 T1, 是求每两个数的 \(lcm\) 的和.

显然啊可以有:

\[ans = \frac{\sum_{i = 1}^n\sum_{j = 1}^n lcm(a_i, j_i) - \sum_{i = 1}^n a}{2}\]

转化成 \(gcd\)

\[ans= \frac{\sum_{i = 1}^n\sum_{j = 1}^n \frac{a_ia_j}{gcd(a_i, j_i)} - \sum_{i = 1}^n a}{2} \]

我们可以枚举 \(gcd\) 然后:

\[\sum_{g = 1}^V \frac{1}{g}\sum_{i = 1}^n\sum_{j = 1}^n a_ia_j [gcd(\frac{a_i}{g}, \frac{a_j}{g}) = 1][g|a_i][g|a_j] \]

对其施加莫比乌斯反演.

然后我们就有

\[\sum_{g = 1}^V \sum_{d = 1}^{\lfloor\frac{V}{g}\rfloor} \frac{\mu(d)}{g}(\sum_{i = 1}^n\sum_{j = 1}^n a_ia_j [dg|a_i][dg|a_j]) \]

我们发现 \(i\)\(j\) 构型是一样的就直接合成平方就好了.

然后我们就可以记:

\[f(T) = \sum_{i = 1}^n a_i [T | a_i] = \sum_{T | k}^V k \cdot b_k \]

然后就出来了.

#include <bits/stdc++.h>
using namespace std;

namespace OI {

#define int long long
#define endl "\n"

constexpr int maxn = 1e6+5;

int mu[maxn];
bool vis[maxn];
vector<int> primes;

int bucket[maxn];
int f[maxn];

int n;
int a[maxn];

constexpr int mod = 998244353;

int inv[maxn];

void main() {
    int sum = 0;
    mu[1] = 1;
    for (int i = 2; i <= 1e6; i++) {
        if (!vis[i]) {
            primes.push_back(i);
            mu[i] = -1;
        }
        for (int p : primes) {
            if (i * p > 1e6)
                break;
            vis[i * p] = 1;
            if (i % p == 0) {
                mu[i * p] = 0;
                break;
            }
            mu[i * p] = -mu[i];
        }
    }
    inv[1] = 1;
    for (int i = 2; i <= 1e6; i++)
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        bucket[a[i]]++;
        sum = (sum + a[i]) % mod;
    }
    for (int i = 1; i <= 1e6; i++) {
        for (int j = 1; i * j <= 1e6; j++)
            f[i] = (f[i] + i * j * bucket[i * j] % mod) % mod;
    }
    int ans = 0;
    for (int g = 1; g <= 1e6; g++) {
        for (int d = 1; d * g <= 1e6; d++) {
            ans = (ans + mu[d] * inv[g] % mod * f[d * g] % mod * f[d * g] % mod + mod) % mod;
        }
    }
    ans = ((ans - sum) % mod + mod) % mod * inv[2] % mod;
    cout << ans << endl;
}

#undef int
#undef endl

}

int main() {
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(0);

    OI::main();

    return 0;
}

小技巧

一个小变化的:

当我们要求一个 \(gcd\) 的和时候,我们可以枚举 \(gcd\) 并保证它确实是 \(gcd\)

\[\sum gcd(a, b) = \sum_g \sum [gcd(\frac{a}{g}, \frac{b}{g}) = 1] \]

很神秘.

还有推论:

\[[gcd(a, b = 1)] = \sum_{d | gcd(x, y)} \mu(d) \]

T2

我上去讲了一下然后讲坏了.

为什么一上台自动解锁大舌头技能啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊.

线性基方法没听懂.

T3

给你一个 \(ABC\) 字符串每次可以删除非 \(AB\) 相邻串. 如果能变成空的我们称它是好的.

给定偶数 \(N\) 记好串数.

我们对于一个序列, 我们必须要同时删除 \(AA\)\(BB\) 这样.

这样 \(A\) \(B\) 不能超过一半.

\[ans = 3 ^ n - 2 \times \sum_{i = \frac{n}{2}}^n \binom{n}{i} 2^{n - i} \]

计数是显然的,trick 是不像人的.

T4

给定一个 \(n\) 构造 \(2n\) 节点的树, 顶点 \(i\)\(i + n\) 的权是 \(i\)\(i\)\(i + n\) 的路径上所有点权异或和为 \(i\) .

两边销了啊, 甭管.

然后我们就要在中间异或.

我们有这样的构造啊,

1 2x
2x 2x+1
1 2x + 1
2x 2x+1

画出来就很显然了. 这里不方便放图.

多出来个 \(1\) 你随便挂就好.

注意到 \(2^k + 2^k\) 放不进去, 尽可能的这样

x a
a 1
1 b + 1
x b + 1

T5

没懂,释怀!

Prufer 序列

今年 NOI 有的玩意.

给你一个有标号的无根树, 每次删掉一个最小的叶子然后把它的父亲加进序列里. 只有一条边我们不删. 我们注意到任取一个序列都可以找到一个树且一一对应.

每个元素的出现次数为度数 - 1

1 4
1 3
4 2
4 5
4 1 4

且随着删除, Prufer 序列的后缀是新树的Prufer 序列. 我们在任何时刻都可以通过Prufer 序列得到每个节点剩下的度数.

我们可以通过Prufer 序列读出所有叶子节点且我们知道谁是唯一的那个最小节点. 然后我们就可以还原这棵树.

Fun Fact: 我把 Prufer 序列听成了剖分序列.

T7

lzy 讲的.

给你一个坐标系, 给定 \(n\) 个点, 坐标是 \((\cos(\frac{2\pi}{L}T_i), \sin(\frac{2\pi}{L}T_i))\) 相当于与单位圆的交点.

求这些点里任意三个点, 求三角形的内心坐标期望.

期望:

\[(\frac{\sum x_i}{\binom{n}{3}}, \frac{\sum y_i}{\binom{n}{3}}) \]

期望是假的.

我们需要求内心的坐标啊.

三条角平分线交一点. 我们设交点是 \(I\) 且延长角平分线为 \(A_1, B_1, C_1\)

则我们可以显然证明 \(I\)\(A_1, B_1, C_1\) 的垂心.

且三个新点恰好是每对弧的中点.

垂心的坐标可以用欧拉线转化, 我们对于任意三角形有结论:外心为 \(O\), 重心为 \(M\), 垂心为 \(H\).

我们可以得到 \(O, M, H\) 三点共线且 \(OH = 3OM\)

注意到重心是好求的:

\[M(\frac{x_1 + x_2 + x_3}{3}, \frac{y_1 + y_2 + y_3}{3}) \]

然后:

\[I(x_1 + x_2 + x_3, y_1 + y_2 + y_3) \]

我们只需要知道任意两点的中点坐标就好了.

我们假设 \(P\) 的极角是 \(\frac{2\pi}{L}T_P\), \(Q\) 的极角是 \(\frac{2\pi}{L}T_Q\) 那么中点 \(M\) 的极角就是它们平均值. 那么我们要统计中点被计算了多少次贡献.

如果 \(R\) 在劣弧上, 产生 \(c\) 的贡献系数,
如果在优弧上, 产生 \(n - 2 - c\) 的贡献.

两点一正一负, \(n - 2 - 2c\) . 然后就完事了.

lzy nb

T6

不会, 释怀!


幸亏衣服是深色的. 似乎有点血迹.

插曲

我被随机抽选留下值日了.

一个人在陌生的城市走夜路, 而且已经八个小时没吃饭了.

好在最后找到了他们并在一家餐馆吃了些东西.

晚上

说实话, 在长春的日子远远没有在北京快乐 —— 大抵是少了那些能活跃气氛的人吧. 没有会议, 没有赶总结, 没有熬夜写题, 只是回到床上开始睡觉.

很累. 好消息是此时身上不怎么疼.

返回目录
posted @ 2026-07-27 16:06  Kibrel  阅读(7)  评论(0)    收藏  举报