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;
}
posted @ 2025-11-10 14:23  zhang_kevin  阅读(22)  评论(0)    收藏  举报