OIFC 2025.11.10 模拟赛总结
寄,$30 + 10 + 4 + 0 = 44pts < $ \(\color{#4191D5}45pts\),可以退役了。
T1 传送门
怎么又是构造啊!没见过的想不出来啊。。。
该练了,菜就多练。
分数:\(\color{Orange}30\) 😦
题目描述
小 M 马上要闯一个传送阵,这个传送阵由 \(K + 1\) 排房间组成,每排有 \(N\) 个房间。第 \(i(i \leq K)\) 排房间与第 \(i + 1\) 排房间间有传送门连接,其中对于 \(\forall i \leq K\) 都存在一个长为 \(N\) 的排列 \(P_i\) 满足第 \(i\) 行第 \(j\) 个房间与第 \(i + 1\) 行第 \(P_{i,j}\) 个房间间有传送阵连接,在闯关过程中可以将排列 \(P_i\) 变为 \(P_i^{-1}\),也就是让第 \(i\) 行第 \(P_{i,j}\) 个房间与第 \(i + 1\) 行第 \(j\) 个房间连接。
小 M 初始在第 \(1\) 行的某个房间,每次只能向编号更大的行走,一直要走到第 \(K + 1\) 行的某个房间。
现在你并不知道他初始会在哪个房间,目标在哪个房间,请你设计这 \(K\) 个排列,使无论初始在哪个房间,目标在个房间,小 M 都可能完成闯关。
数据范围:\(3 \leq N \leq 1000\),\(\lceil \log_2 N \rceil + 1 \leq K \leq 1000\)。
题解
构造题。
显然可以通过大量 \(1 \sim n\) 的顺序排列浪费没用完的步骤。
首先,部分分中有一个性质是奇数,所以先考虑奇数怎么做。
考场上通过观察暴力的构造,发现奇数就是把 \(1 \sim n\) 的顺序排列每次右移 \(2^i\) 位。
然后通过 checker 验证,发现 \(1000\) 以内全部奇数都成立!于是得到了这 \(20\) 分。
给出 \(20\) 分参考代码:
//20 pts
#include<bits/stdc++.h>
using namespace std;
signed main(){
freopen("deliver.in", "r", stdin);
freopen("deliver.out", "w", stdout);
int n, k; cin >> n >> k;
int realk = ceil(log2(n)) + 1;
for(int i = 1; i <= realk; i++){
for(int j = 1; j <= n; j++){
int p = ((j - (1 << i)) % n + n) % n;
cout << (p ? p : n) << " \n"[j == n];
}
}
for(int i = realk; i < k; i++){
for(int j = 1; j <= n; j++){
cout << j << " \n"[j == n];
}
}
return 0;
}
但下一个特殊性质 \(n = 2^x\) 想不出来怎么做了。
考试的时候花了 \(2\) 小时不知道在想什么,最后凑了个 \(n \leq 5\) 的部分分就走了。
赛后继续考虑如何构造。
注意到我们会了奇数怎么做,而且偶数只比奇数多 \(1\)。因此考虑怎么完成奇数的情况再加一个新的数。
如果先让新加的数死在自己的位置,则能够用 \(\lceil \log_2 N \rceil - 1\) 次完成左边奇数部分的构造。
因此我们可以加至多 \(2\) 个排列,使新的数可以到达左边所有,左边所有可以到达新的数。
对于左边 \(n - 1\) 个到达右边,可以在最后加一行排列 \(\{2,3,4, \cdots ,n-1,n,1\}\),使得所有左边的 \(n - 1\) 个数能够通过这个排列到达所有 \(1 \sim n-1\) 和 \(n\) 这 \(n\) 个位置。
对于右边的那一个,可以刚开始加一行排列 \(\{n,1,2,\cdots,n-2,n-1\}\),来保证编号为 \(n\) 的能够出去,参与左边的构造。注意到,每个点都有两种不同的选择,因此不会出现一个点只能在第 \(n\) 个位置出不去。
P.S. 草稿:\(\color{#4191D5}\texttt{Here}\)。
参考代码:
#include<bits/stdc++.h>
using namespace std;
signed main(){
freopen("deliver.in", "r", stdin);
freopen("deliver.out", "w", stdout);
int n, k; cin >> n >> k;
if(n & 1){
int realk = ceil(log2(n)) + 1;
for(int i = 1; i <= realk; i++){
for(int j = 1; j <= n; j++){
int p = ((j - (1 << i)) % n + n) % n;
cout << (p ? p : n) << " \n"[j == n];
}
}
for(int i = realk; i < k; i++){
for(int j = 1; j <= n; j++){
cout << j << " \n"[j == n];
}
}
}else{
for(int i = 1; i <= n; i++) cout << ((i + 1) % n ? (i + 1) % n : n) << " \n"[i == n];
int realk = ceil(log2(n - 1)) - 1;
for(int i = 1; i <= realk; i++){
for(int j = 1; j < n; j++){
int p = ((j - (1 << i)) % (n - 1) + (n - 1)) % (n - 1);
cout << (p ? p : n - 1) << ' ';
}
cout << n << '\n';
}
for(int i = 1; i <= n; i++) cout << ((i - 1) % n ? (i - 1) % n : n) << " \n"[i == n];
for(int i = realk + 2; i < k; i++){
for(int j = 1; j <= n; j++){
cout << j << " \n"[j == n];
}
}
}
return 0;
}
T2 岁月
寄。
分数:\(\color{Red}10\) 😦
题目描述
形式化题意:
给定一个正整数 \(k\) 和一个长为 \(n\) 的数组 \(\{a\}\),保证对于 \(\forall 1 \leq i \leq n\),都有 \(0 \leq a_i < 2^k\),且对于 \(\forall 1 \leq i < n\),都有 \(a_i \leq a_{i+1}\)。
定义如下函数:
\(c(n,x) = \max\limits_{i = 0}^x (n \oplus i)\)
\(f(n,x) = \max\limits_{i = 0}^n c(a_i,x)\)
\(g(n,x) = \sum\limits_{i = 1}^n [c(a_i,x) = f(n,x)]\)
你需要求出 \(\sum\limits_{x = 0}^{2^k - 1} f(n,x)\) 和 \(\sum\limits_{x = 0}^{2^k - 1} g(n,x)\) 的值,答案对 \(2^{64}\)取模。
题解
先考虑如何求第一问。
首先,我们可以通过瞪眼 01 Trie 发现,\(f(n,x) = c(a_n,x)\)。
那么,我们考虑 \(x\) 会产生哪些贡献。
令 \(f_i\) 表示填 \(x\) 时,填到了最高位为 \(i\),时的贡献。显然初始值是 \(f_0 = a_n\),答案是 \(\sum\limits_{i = 0}^k f_i\)。
注意到,如果 \(a_n\) 的第 \(i\) 位是 \(1\),则单次贡献一定为 \(a_i | (2^{i - 1}-1)\),贡献次数为 \(2^{i - 1}\)。因为此时,第 \(i\) 位以下可以随意填写,\(i\) 位以上则保持不变。贡献次数显然是 \(2^{i - 1}\),因为你只能填写这么多位。
而如果 \(a_n\) 的第 \(i\) 位是 \(0\),则显然有贡献 \(\sum\limits_{j = 1}^{i - 1} f_j\),因为下面的贡献一样。同时,还有 \(2^{i - 1}\) 次 \(2^{i - 1}\) 大小的共线。因此得到转移方程 \(f_i = \sum\limits_{j = 1}^{i - 1} f_j + (2^{i - 1})^2\)。
这样,我们就在 \(\mathcal{O}(k^2)\) 的时间复杂度内解决了第一问。
对于第二问,我们枚举所有的 \(a_i\),计算多少 \(x\) 使其产生贡献。
显然,我们可以求出 \(a_i\) 和 \(a_n\) 的“lc非p”,也就是第一个不一样的位置。那么显然,我们可以用 \(2^k\) 减去不产生贡献的 \(x\)。而不产生贡献的 \(x\) 如果最高位过于“lc非p”,则可以任选;否则需要找到“lc非p”以下的所有 \(0\) 位置,计算贡献。找到一个数的非 \(0\) 位很简单,只需要先找到 \(1\) 然后异或即可,详见代码。
最后就是,虽然使用 unsigned long long 取模,但左移超过 \(64\) 位是 UB,要注意这一点!
参考代码:
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return x;
}
int n, k, a[5000005], f[64];
inline int fSolve(){
f[0] = a[n];
for(int i = 1; i <= k; i++){
if((a[n] >> (i - 1)) & 1){
f[i] = (1ull << (i - 1)) * (((1ull << (i - 1)) - 1) | a[n]);
}else{
int fsum = 0;
for(int j = 0; j < i; j++) fsum += f[j];
f[i] = fsum + (1ull << (i - 1)) * (1ull << (i - 1));
}
}
int res = 0;
for(int i = 0; i <= k; i++) res += f[i];
return res;
}
inline int gSolve(){
int res = 0;
for(int i = 1; i <= n; i++){
if(a[i] == a[n]){
res += (1ull << k);
}else{
int lcnotp = -1;
for(int j = k; j >= 1; j--){
if(((a[i] >> (j - 1)) & 1) != ((a[n] >> (j - 1)) & 1)){
lcnotp = j;
break;
}
}
int cnt = 0;
for(int j = lcnotp + 1; j <= k; j++) cnt += !((a[i] >> (j - 1)) & 1);
res += (1ull << k) - (1ull << cnt) * (((a[i] & ((1ull << (lcnotp - 1)) - 1)) ^ ((1ull << (lcnotp - 1)) - 1)) + (1ull << (lcnotp - 1)));
}
}
return res;
}
signed main(){
freopen("years.in", "r", stdin);
freopen("years.out", "w", stdout);
n = read(), k = read();
for(int i = 1; i <= n; i++) a[i] = read();
cout << fSolve() << ' ' << gSolve() << '\n';
return 0;
}

浙公网安备 33010602011771号