OIFC 2025.11.17 模拟赛总结

自己学校出的,我们提前考完当验题了(

\(100 + 15 + 28 + 12 = 155pts\),还凑活吧。

T1 休息时间

终于场切 T1 了!

题目描述

汤圆的脑容量是一个常数 \(K\),若当前汤圆的 失眠指数 (Insomnia Quotient,简称 IQ) 为 \(p\),那么它可以解决难度在 \([p, p + K]\) 之间的题目。

每道题目都有一个难度,若当前汤圆的 IQ 为 \(p\),当汤圆做一道难度为 \(a_i\) 的题目的时候,会发生以下事情之一:

  • \(a_i \in [p, p + K]\),则汤圆会做出这道题目,\(p\) 不变。
  • \(a_i < p\),则汤圆无法做出这道题目,同时汤圆会自动适应,它的 IQ 会 \(-1\),即 \(p \leftarrow p - 1\)
  • \(a_i > p + K\),则汤圆无法做出这道题目,同时汤圆会自动适应,它的 IQ 会 \(+1\),即 \(p \leftarrow p + 1\)

初始汤圆的 IQ 为 \(p_0\),这 \(n\) 道题目的难度为 \(a_1, a_2, \cdots a_n\),汤圆需要安排一个顺序做这 \(n\) 道题目,每道题目无论是否做出来都只能做恰好一次。

求汤圆最多能做出来多少题目。

本题有多组数据。

数据范围:\(1 \leq n \leq 10^6\)\(\sum n \leq 10^6\)\(0 \leq a_i, K, p_0 \leq 10^9\)

题解

显然解决的问题是一段连续区间,考虑枚举其中一个的端点。

目前有两种情况,一种是先完成左边的,另一种是先完成右边的(左右走来走去肯定是不优的)。

\(b_i = a_i + i\),枚举的左/右端点是 \(L,R\)

对于先完成左边:

  • 能够到达最左边:\(p_0 - a_L \leq L - 1\)

  • 如果想要回到最右边:\(a_R - \min(p_0 + K, a_L + K) \leq n - R\),等价于:

\[a_R + R \leq n + \min(p_0 + K, a_L + K) \]

\[b_R \leq n + \min(p_0 + K, a_L + K) \]

显然希望长度长,说白了就是 \(R\) 尽量大 (\(L \leq R \leq n\))。所以二分找到第一个严格大的再减 \(1\) 即可。


对于先完成右边:

  • 能够到达最右边:\(a_R - (p_0 + K) \leq n - R\)

  • 如果想要回到最左边:\(\max(p_0, a_R - K) - a_L \leq L - 1\),等价于:

\[\max(p_0, a_R - K) + 1 \leq a_L + L \]

\[b_L \geq \max(p_0, a_R - K) + 1 \]

显然希望长度长,说白了就是 \(L\) 尽量小 (\(1 \leq L \leq R\))。所以二分找到第一个大于等于的就行了。

最后两种情况取 \(\max\) 就行了。应该是 \(\mathcal{O}(n \log n)\) 的。

参考代码(C++):

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)){
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
inline void write(int x){
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}
int n, K, p0, a[1000005], b[1000005];
inline void Solve(){
    n = read(), K = read(), p0 = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; i++) b[i] = a[i] + i;
    int ans = 0;
    for(int mostl = 1; mostl <= n; mostl++){
        if(p0 - a[mostl] <= mostl - 1){
            int mostr = upper_bound(b + mostl, b + 1 + n, n + min(p0 + K, a[mostl] + K)) - b - 1;
            if(mostl <= mostr && mostr <= n){
                if(a[mostr] - min(p0 + K, a[mostl] + K) <= n - mostr){
                    ans = max(ans, mostr - mostl + 1);
                }
            }
        }
    }
    for(int mostr = 1; mostr <= n; mostr++){
        if(a[mostr] - (p0 + K) <= n - mostr){
            int mostl = lower_bound(b + 1, b + 1 + mostr, max(p0, a[mostr] - K) + 1) - b;
            if(1 <= mostl && mostl <= mostr){
                if(max(p0, a[mostr] - K) - a[mostl] <= mostl - 1){
                    ans = max(ans, mostr - mostl + 1);
                }
            }
        }
    }
    write(ans), putchar('\n');
    return;
}
signed main(){
    freopen("iq.in", "r", stdin);
    freopen("iq.out", "w", stdout);
    int T = read();
    while(T--) Solve();
    return 0;
}

T2 兼职时间

拿到了预期的部分分。

题目描述

汤圆看管的区域是一个长为 \(T\) 的环,环上的位置从原点开始顺时针用 \([0, T)\) 编号。

汤圆有 \(m\) 个摄像头,第 \(i\) 个摄像头的监视范围为 \([l_i, r_i)\),其中 \(l_i \not= r_i\),表示从 \(l_i\) 顺时针走到 \(r_i\) 的这段范围,具体地:

  • \(l_i < r_i\),则监视的范围为 \([l_i, r_i)\)

  • \(l_i > r_i\),则监视的范围为 \([l_i, T) \cup [0, r_i)\)

现在汤圆要在这里兼职 \(q\) 天,第 \(i\) 天晚上会有 \(n_i\) 个玩偶出来游荡,这些玩偶初始位置分别是 \(x_{i,1}, x_{i,2}, \cdots, x_{i, n_i}\)

当不被摄像头监视时,玩偶会以 \(1\) 单位距离每单位时间的速度在环上顺时针行动。请注意运动是连续的。

但汤圆可以影响这些玩偶的移动:每一实数时刻,汤圆可以选择看至多一个摄像头,当汤圆看着一个摄像头的时候,这个摄像头监视范围内的玩偶都会静止不动(在汤圆看着一个摄像头的时候从监视范围外进入到监视范围内的玩偶也会在刚好进入监视范围时静止不动)。

为了节省电量,汤圆希望用尽可能短的时间将所有玩偶聚到同一个点上,请对每天晚上输出这个最短时间,帮助汤圆安全度过夜晚。

\(1 \leq m \leq 10^5\)\(1 \leq T \leq 5000\)\(2 \leq n_i \leq T\)\(\sum\limits n_i \leq 10^5\)\(0 \leq l_i, r_i < T\)\(l_i \not= r_i\)\(0 \leq x_{i, 1} < x_{i,2} < \cdots < x_{i, n_i} < T\)

题解

首先,注意到无论怎么移动,这些点的相对位置都是不会变的。因此,所有点位置重合相当于两个相邻的点 \(x,y\),满足 \(x\) 依次追上其他所有点后追上了 \(y\)

因此,我们把题目转化为了 \(2\) 个人的情况。我们可以预处理一个大小为 \(T^2\) 的数组,表示一个点追上另一个的最小代价,然后就可以线性处理询问了。

对于两个人的情况,我们可以注意到,让两个人一起不动是不优的,控制住追逐的人,放走另一个也是不优的。因此,我们的策略是,如果存在一个区间,包含逃跑的人单不包含追逐的人,我们就一直选择这个区间直到逃跑的人赶到这个区间,重复这个过程。

现在的问题就是求是否有区间包含 \(x\) 但是不包含 \(y\) 了。这可以使用二维差分解决。具体的,在一个平面直角坐标系中,对于一个区间 \([l,r]\),我们在 \(x\) 轴和 \(y\) 轴上找到它,组成的矩形就对应着同时包含 \(x\)\(y\) 的区间。我们需要做的是把包含 \(x\) 但不包含 \(y\) 的区间所对应的矩形全部 \(+1\),因此可以使用二维差分解决。

这样总时间复杂度为 \(\mathcal{O}(T^2 + m + \sum n)\),可以通过。

参考代码(C++):

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-'){
            f = -1;
        }
        ch = getchar();
    }
    while(isdigit(ch)){
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
inline void write(int x){
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}
int m, T, q, dis[5005][5005], x[5005];
namespace DS{
    #define y1 zhang_kevin
    int a[5005][5005], d[5005][5005];
    inline void Modify(int x1, int y1, int x2, int y2){
        x1++, y1++, x2++, y2++;
        d[x1][y1]++;
        d[x2 + 1][y1]--;
        d[x1][y2 + 1]--;
        d[x2 + 1][y2 + 1]++;
        return;
    }
    inline void Recover(){
        for(int i = 1; i <= T; i++){
            for(int j = 1; j <= T; j++){
                a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + d[i][j];
            }
        }
        return;
    }
    inline bool query(int x, int y){
        return a[x + 1][y + 1];
    }
}
signed main(){
    freopen("cell.in", "r", stdin);
    freopen("cell.out", "w", stdout);
    m = read(), T = read();
    for(int i = 1; i <= m; i++){
        int l = read(), r = read();
        if(l < r){
            DS::Modify(l, 0, r - 1, l - 1);
            DS::Modify(l, r, r - 1, T);
        }else{
            DS::Modify(0, r, r - 1, l - 1);
            DS::Modify(l, r, T, l - 1);
        }
    }
    DS::Recover();
    for(int len = 1; len < T; len++){
        for(int l = T - 1; l >= 0; l--){
            int r = (l + len) % T;
            if(DS::query(r, l)) dis[l][r] = dis[(l + 1) % T][r] + 1;
            else dis[l][r] = dis[(l + 1) % T][(r + 1) % T] + 1;
        }
        for(int l = T - 1; l >= 0; l--){
            int r = (l + len) % T;
            if(DS::query(r, l)) dis[l][r] = dis[(l + 1) % T][r] + 1;
            else dis[l][r] = dis[(l + 1) % T][(r + 1) % T] + 1;
        }
    }
    q = read();
    while(q--){
        int n = read();
        for(int i = 1; i <= n; i++) x[i] = read();
        int ans = INT_MAX;
        for(int i = 1; i <= n; i++)
            ans = min(ans, dis[x[i % n + 1]][x[i]]);
        write(ans), putchar('\n');
    }
    return 0;
}
posted @ 2025-11-17 15:44  zhang_kevin  阅读(12)  评论(0)    收藏  举报