Day 2
Day 2
昨天晚上睡挺好.
上午
T1
给定操作 \(shuffle(l, r)\) 和序列 \(a = {1, 2, 3, \cdots, n}\).
如果 \(l = r - 1\) 则 \(swap(a_l, a_r)\), 否则 \(shuffle(l, r) = shuffle(l, r - 1), shuffle(l + 1, r)\) .
我们注意到一下几个性质(打表得):
- 对于 \(r - l + 1\) 为偶数, \(shuffle\) 两次可以归零.
- 对于 \(r - l + 1\) 为 \(2^k\) 则 \(shuffle\) 后相邻两位交换.
然后展开 \(shuffle\) 可以得到:
然后我们可以递归求解. 如果是奇数就展开成偶数.
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
int T, n, q;
int f(int l, int r, int q) {
if (l > q || r < q)
return q;
int k = __lg(r - l + 1);
if(r - l + 1 == (1ll << k)){
if (l & 1)
return (q & 1) ? q + 1 : q - 1;
else
return (q & 1) ? q - 1 : q + 1;
}
if (q <= r - (1ll << k))
return f(l, r - (1ll << k), q);
if (q >= l + (1ll << k))
return f(l + (1ll << k), r, q);
return q;
}
void main() {
cin >> T;
while (T--) {
cin >> n >> q;
int k = __lg(n);
if (n & 1)
cout << f(1, n - 1, f(2, n, q)) << endl;
else
cout << f(1, n, q) << endl;
}
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
警示后人:递归要判定 \(q \in [l, r]\) !!!
不然奇数查边界的时候会挂.
有个小孩哥(昨天 AK 了)给我首切抢了.
释怀了, 人家都 AK 了我还能说什么.
T2
T3
警示后人啊警示后人, 不要再 vjudge 上看见题目名就去洛谷上搜, 可能不是一道题.
T2
怎么说呢这个 T2 的博弈论好像是假的.
给你 \(2n\) 个整数和一个模数 \(m\), 两个人依次在黑板上拿一个数字, 拿完时判断两个人的数字总和是否对 \(m\) 同余. 如果是, 后手胜.
问后手是否有机会胜利.
不要被唬住啊, 我们发现如果后手胜利有这样的条件:
看一下第二个条件啊, 我们注意到 \(A \equiv B\) 那么显然对
那么我们不妨给每一个 \(a_i \times 2 \bmod m\) 并记录下奇偶性, 就可以轻松知道后手是否可以跟随先手控制 TA 达到获胜条件.
也就是说,
且
则后手胜, 反之先手胜.
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
int n, m;
map<int, int> bucket;
void main() {
cin >> n >> m;
int sum = 0;
for (int i = 1; i <= 2 * n; i++) {
int a;
cin >> a;
sum = (sum + a) % m;
bucket[2 * a % m]++;
}
int newS = 0;
for (auto p : bucket) {
if (p.second & 1) {
puts("Alice");
return;
}
newS = (newS + p.first * p.second / 2) % m;
}
if (newS == sum)
puts("Bob");
else
puts("Alice");
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
右边有个哥们在玩 Evolve.
顺手写了一篇 T2 的 题解 不知道能不能过审核。
午休
吃了碗自热米饭, 然后是抖音 + 三国杀.
下午
T5
构造一个 \(n \times m\) 的矩阵且值域为 \([1, 25]\) 使得不存在一个矩形的四个顶点权值相等.
我们可以把一个巨大的矩阵分割成若干个比较小的数独, 然后通过拼接这些数独得出矩阵.
众所周知, 一个边长为 \(n\), 值域为 \([0, n)\) 且左上角为 \(k\) 的数独, 它的每一位是:
\(i, j\) 是 \(0\) 起的.
那么我们要开始拼接这些数独了. 怎么拼呢?
存在 \((x_1, y_1)\) 与 \((x_2, y_2)\) 为顶点的矩形使得四顶点相等, 必然有四个顶点在 \(M_\{a, b, c, d\}\).
得到下面的式子:
得到
然后我们要构造不满足上面条件的 \(a, b, c, d\)
现在让 \(a = x_1'y_1', b = x_1'y_2', c = x_2'y_1', d = x_2'y_2'\)
我们有:
所以我们要找一种 \(n\) 使得
显然当 \(n\) 是质数的时候成立.
我们取 \(n = 23\) 就好了.
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
int n, m;
constexpr int block = 23;
// 这里为了方便理解用 piece 函数表示这是哪个数独块.
inline int piece(int id, int x, int y) {
int f = (id + x) % block;
int b = (f + y) % block;
return b;
}
map<pair<int, int>, set<int>> mp;
void main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << piece((i / block) * (j / block) % block, i % block, j % block) + 1 << ' ';
}
cout << endl;
}
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
这篇我也扔题解了. 并且我发现上午的题解没提审.
Damn it.
一些杂项
然后我花了大量时间去用 A* 写 T5 当然不能过.
然后我上去讲了下 T2, 还行.
T3 没听懂, 有什么线性同余方程?EEEE
大家为什么 T3 都 TLE 了?
T4
给你若干个电阻, 你要构造一个电阻让他的电阻值为 \(\sqrt{D}\)
如果是串联, \(R' = \sum_i R_i\)
并联是 \(R' = \frac{1}{\sum_i \frac{1}{R_i}}\)
我们要知道任意的 \(\sqrt{D} = a_i + \frac{1}{a_2 + \frac{1}{a_3 + \cdots}}\)
然后就归纳下去……?
嗯。没懂。我物理不好。
甩给我们物理老师吧!

后来还是明白了, 就是不断递归然后构造. 不大想写.
T5
上去讲了.
T6
不会. 不是, 今天是数论场还是构造场啊……
此题 已点踩. 写本日记时已经达到 QOJ -135 分. 甚至超越了排水系统!
下午休息
lzy 和 zyq 去琴房了, lyd 不知道去哪里了.
我在东师游荡了几圈就回机房开始捅咕 [NOI 2026] 布丁 了.
晚自习
因为讲题拿了瓶饮料.

[NOI 2026] 布丁
题意简述
交互题, 给定值域 \(m (m \le 3000)\) 以及若干数 \(w\) .
\(t \le 3000\) 次提问, 每次在交互库内部钦定一个值 \(w\), 你可以进行若干次询问, 每次给出一个序列, 交互库会将 \(w\) 插入你的序列中并排序, 返回每相邻两个数的 \(gcd\) 的和.
做法
考虑决策树. 在决策树上的每个节点存储:
- 当前的查询操作
- 当前的候选值
随机构造若干个序列, 然后按信息熵排序, 取信息熵最高的序列并作为本节点的查询序列.
对所有候选值进行虚拟查询, 然后将查询结果相同的节点归入同一节点. 依次向下生长, 直到:
- 此节点候选数列长度小于你的生成序列长度(9)
- 此节点深度为 \(8\) (很重要, 你需要保证查询次数为 \(4\) 因为 \(5\) 会扣很多分)
叶子节点的查询序列就是候选序列.
这样分数不够好, 那么我们需要做生成的查询序列的随机数池.
保证随机数池的质因子足够小, 这样可以让 \(gcd\) 比较大且不会那么奇奇怪怪.
我的池子是 \(2, 3, 5, 7, 11, 13\) 的所有倍数.
由此, 生成一颗决策树并拿到 \(90\) 左右的分数, 上限 \(96pts\).
Talk is Cheap
#include <bits/stdc++.h>
#include "pudding.h"
using namespace std;
namespace OI {
random_device rd;
mt19937 gen(rd());
inline int randint(int l, int r) {
uniform_int_distribution<> dis(l, r);
return dis(gen);
}
int pool[] = {
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 22, 24,
25, 26, 27, 28, 30, 32, 33, 35, 36, 39, 40, 42, 44, 45, 48, 49, 50, 52,
54, 55, 56, 60, 63, 64, 65, 66, 70, 72, 75, 77, 78, 80, 81, 84, 88, 90,
91, 96, 98, 99, 100, 104, 105, 108, 110, 112, 117, 120, 121, 125, 126,
128, 130, 132, 135, 140, 143, 144, 147, 150, 154, 156, 160, 162, 165,
168, 169, 175, 176, 180, 182, 189, 192, 195, 196, 198, 200, 208, 210,
216, 220, 224, 225, 231, 234, 240, 242, 243, 245, 250, 252, 256, 260,
264, 270, 273, 275, 280, 286, 288, 294, 297, 300, 308, 312, 315, 320,
324, 325, 330, 336, 338, 343, 350, 351, 352, 360, 363, 364, 375, 378,
384, 385, 390, 392, 396, 400, 405, 416, 420, 429, 432, 440, 441, 448,
450, 455, 462, 468, 480, 484, 486, 490, 495, 500, 504, 507, 512, 520,
525, 528, 539, 540, 546, 550, 560, 567, 572, 576, 585, 588, 594, 600,
605, 616, 624, 625, 630, 637, 640, 648, 650, 660, 672, 675, 676, 686,
693, 700, 702, 704, 715, 720, 726, 728, 729, 735, 750, 756, 768, 770,
780, 784, 792, 800, 810, 819, 825, 832, 840, 845, 847, 858, 864, 875,
880, 882, 891, 896, 900, 910, 924, 936, 945, 960, 968, 972, 975, 980,
990, 1000, 1001, 1008, 1014, 1024, 1029, 1040, 1050, 1053, 1056, 1078,
1080, 1089, 1092, 1100, 1120, 1125, 1134, 1144, 1152, 1155, 1170, 1176,
1188, 1200, 1210, 1215, 1225, 1232, 1248, 1250, 1260, 1274, 1280, 1287,
1296, 1300, 1320, 1323, 1331, 1344, 1350, 1352, 1365, 1372, 1375, 1386,
1400, 1404, 1408, 1430, 1440, 1452, 1456, 1458, 1470, 1485, 1500, 1512,
1521, 1536, 1540, 1560, 1568, 1575, 1584, 1600, 1617, 1620, 1625, 1638,
1650, 1664, 1680, 1690, 1694, 1716, 1728, 1750, 1755, 1760, 1764, 1782,
1792, 1800, 1815, 1820, 1848, 1872, 1875, 1890, 1911, 1920, 1925, 1936,
1944, 1950, 1960, 1980, 2000, 2002, 2016, 2025, 2028, 2048, 2058, 2079,
2080, 2100, 2106, 2112, 2156, 2160, 2178, 2184, 2187, 2197, 2200, 2205,
2240, 2250, 2268, 2288, 2304, 2310, 2340, 2352, 2376, 2400, 2401, 2420,
2430, 2450, 2457, 2475, 2496, 2500, 2520, 2548, 2560, 2574, 2592, 2600,
2625, 2640, 2646, 2695, 2700, 2704, 2730, 2744, 2750, 2772, 2800, 2808,
2835, 2860, 2880, 2912, 2916, 2925, 2940, 2970, 3000
};
const int POOL_SIZE = sizeof(pool) / sizeof(pool[0]);
int simulate(const vector<int>& a, int w) {
vector<int> b;
b.reserve(a.size() + 1);
int i = 0;
while (i < (int)a.size() && a[i] <= w) {
b.push_back(a[i]);
++i;
}
b.push_back(w);
while (i < (int)a.size()) {
b.push_back(a[i]);
++i;
}
int res = 0;
for (int j = 1; j < (int)b.size(); ++j) {
res += __gcd(b[j - 1], b[j]);
}
return res;
}
double entropy(const map<int, int>& cnt, int total) {
double ret = 0.0;
for (auto p : cnt) {
double prob = p.second * 1.0 / total;
ret -= prob * log2(prob);
}
return ret;
}
struct Node {
vector<int> cand;
vector<int> query;
map<int, int> child;
int depth = 0;
};
vector<Node> tree;
const int TRIES = 8000;
int buildNode(const vector<int>& cand, int depth = 0) {
int u = tree.size();
tree.push_back({cand, {}, {}, depth});
if (cand.size() <= 8 || depth == 3) {
tree[u].query = cand;
return u;
}
double bestEntropy = -1e9;
vector<int> bestQuery;
for (int tries = 0; tries < TRIES; ++tries) {
set<int> qset;
while ((int)qset.size() < 8) {
qset.insert(pool[randint(0, POOL_SIZE - 1)]);
}
vector<int> query(qset.begin(), qset.end());
map<int, int> cnt;
for (int x : cand) {
cnt[simulate(query, x)]++;
}
double ent = entropy(cnt, cand.size());
if (ent > bestEntropy) {
bestEntropy = ent;
bestQuery = query;
}
}
tree[u].query = bestQuery;
map<int, vector<int>> groups;
for (int x : cand) {
int val = simulate(bestQuery, x);
groups[val].push_back(x);
}
for (auto& pr : groups) {
int val = pr.first;
const vector<int>& childCand = pr.second;
int v = buildNode(childCand, depth + 1);
tree[u].child[val] = v;
}
return u;
}
int root = -1;
} // namespace OI
using namespace OI;
void init(int, int) {
vector<int> all;
for (int i = 1; i <= 3000; ++i)
all.push_back(i);
tree.clear();
root = buildNode(all);
}
int find_tastiness(int c, int m) {
int u = root;
while (true) {
int ret = query_tastiness(tree[u].query);
int s = u;
u = tree[u].child[ret];
if (!u) {
for (int x : tree[s].cand)
if (ret == simulate(tree[s].query, x))
return x;
}
}
return -1;
}
爽飞!
一些杂项
在改昨天测试的根号分治, 结果学校评测机卡常. 几毫秒误差. 有点破防.
晚上
进行了别样的飞行棋大战, 大败而归.
皮肤状态良好, 应该是 NOI 那道题让我爽到了.

浙公网安备 33010602011771号