2026/09/23 模拟赛总结

NOIP 模拟赛第十场

预估分数:\(100+100+8+45\),实际分数 \(100+70+8+45\),\(rk1/12\)。

Lemon 评测机:\(100+100+8+45\),\(rk1/16\)。

终于考好了一次了,虽然 T2 被卡了 \(30\) 分的常。

事实上我的名次也并不是名副其实,因为后面有人被卡了 \(100\) 分的常还剩 \(200\) 分。于是他成为了唯一一个只通过 T4 的人

T1

题目大意

题目名称:星穹铁道

有 \(n\) 个站点,第 \(i\) 个站点坐标为 \(a_i\)。你现在有 \(Q\) 次操作,每次操作指定三个数 \(x,y,z\),将所有满足 \(a_i \bmod x = y\) 的 \(a_i\) 全部加上 \(z\),数据保证 \(|z| = 1\)。最后你要求出坐标在 \([L,R]\) 的站台个数是多少,并按顺序输出满足条件的站台编号。

\(n,Q \le 10^6,0 \le y < x \le 10^9,0 \le a_i \le 10^9\)

思路分析

有点诈骗的题。但其实我在场上 \(20min\) 就过了全部样例,因为我见过类似的 trick。

首先正推不好推,最多就是个 \(\mathcal{O}(n^2)\) 的复杂度。但是注意到性质是 \(|z| = 1\),所以才想到 \(a_i\) 的相对顺序是不变的,也就是说,无论 \(x\) 和 \(y\) 取何值,只要初始状态 \(a_i \le a_j\),则最后的 \(a_i\) 也一定 \(\le a_j\)。所以最后的满足条件的 \(a\) 一定是一段连续的区间(排序后)。

于是考虑倒推。假设第 \(i\) 次操作结束后满足条件的区间是 \([L,R]\),那么第 \(i - 1\) 次操作结束后满足条件的区间涨什么样呢?下文为了方便,假设 \(z = 1\),因为 \(z = -1\) 与之是镜像的。

  • \(L\) 的变化:如果 \((L - 1) \bmod x = y\),则 \(L\) 可以往左移至 \(L - 1\)。因为在第 \(i\) 步中位置在 \(L - 1\) 的站点肯定会加上一来到位置 \(L\)。

  • \(R\) 的变化:如果 \(R \bmod x = y\),则 \(R\) 往左移至 \(R - 1\)。因为在第 \(i\) 步中位置在 \(R\) 的站点会加上一来到 \(R + 1\),所以超出区间范围,因此需要将其删去。

\(z = -1\) 同理,镜像操作即可。

注意有可能下标会出现负数需要对其取模。

初始题面是没有 \(y < x\) 这个条件的,然后神秘搬运工在看到我在讨论帖发的 hack 之后紧急加上了。

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6 + 5;
int n,T,X[N],Y[N],Z[N],L,R;
int a[N];
vector<int>ans;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    freopen("star.in","r",stdin);
    freopen("star.out","w",stdout);
    cin >> n >> T;
    for(int i = 1;i <= n;i ++)cin >> a[i];
    for(int i = 1;i <= T;i ++)
        cin >> X[i] >> Y[i] >> Z[i];
    cin >> L >> R;
    for(int i = T;i >= 1;i --)
    {
        Y[i] = (Y[i] % X[i] + X[i]) % X[i];
        if(Z[i] == 1)
        {
            if(((L - 1) % X[i] + X[i]) % X[i] == Y[i])L --;
            if((R % X[i] + X[i]) % X[i] == Y[i])R --;
        }
        else
        {
            if((L % X[i] + X[i]) % X[i] == Y[i])L ++;
            if(((R + 1) % X[i] + X[i]) % X[i] == Y[i])R ++;
        }
    }
    for(int i = 1;i <= n;i ++)
        if(a[i] >= L && a[i] <= R)ans.push_back(i);
    cout << ans.size() << '\n';
    for(int i = 0;i < ans.size();i ++)cout << ans[i] << " ";
}

T2

题目大意

给定两个整数 \(n,m\),求最小的 \(k\),使得对于一排 \(k\) 个球(第 \(i\) 个球编号为 \(i\)),使得无论每个球如何赋权(权值只能为 \(0\) 或 \(1\)),都能找出 \(n + 1\) 个权值为 \(0\) 的球满足前 \(n\) 个球编号相加小于第 \(n + 1\) 个球的编号,或找出 \(m + 1\) 个权值为 \(1\) 的球满足前 \(m\) 个球编号相加小于第 \(m + 1\) 个球的编号。\(T\) 组数据。

\(n,m,T \le 10^6\)

思路分析

我们记 \(s(l,r) = \frac{(l + r)(r - l + 1)}{2}\)。

首先最简单的想法就是前面放 \(s(1,n)\) 个 \(0\),后面放 \(s(s(1,n) + 1,s(1,n) + m) + 1 - s(1,n)\) 个 \(1\),然后 \(n\) 和 \(m\) 交换一下顺序取 \(\max\) 即可。

但是通过手玩样例就会发现这个方法在 \(n,m \ge 5\) 时就会挂,我们尝试其他构造:发现有时候 \(11...11000...00011...11\) 这个构造要比 \(000...00011..11\) 会更优。

当然这时候 \(1\) 和 \(0\) 交换顺序显然是不优的,因为这样前 \(n\) 个 \(0\) 位置和就会变小。因此朴素想法就是枚举上面第一段 \(1\) 的个数,然后再进按上面的公式操作,复杂度 \(\mathcal{O}(T\max(n,m))\),只有 \(40pts\) 左右的分。

我们尝试一些优化,又经过手玩样例发现这个答案是单峰的,所以我们三分找到一个数 \(k\) 使得前面加入 \(k\) 个 \(1\) 时结果最小即可。

其实该函数为二次函数,所以可以直接 \(\mathcal{O}(1)\) 找对称轴,复杂度 \(\mathcal{O}(T)\)。但是我只能说,我的代码卡卡应该也是能过的,虽然赛时确实疏忽了。把 #define int long long 改成 #define int unsigned long long 跑的飞快。

#include <iostream>
#include <cmath>
#define int unsigned long long
using namespace std;
int n,m,T;
int check(int i)
{
    int last = (i + 1 + i + n) * n / 2 + 1;
    return (1 + i) * i / 2 + (last + last + m - i - 1) * (m - i) / 2 + 1;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    freopen("power.in","r",stdin);
    freopen("power.out","w",stdout);
    cin >> T;
    while(T --)
    {
        cin >> n >> m;
        int last = (1 + n) * n / 2 + 1;
        int ans1 = (last + last + m - 1) * m / 2 + 1;
        swap(n,m);
        last = (1 + n) * n / 2 + 1;
        int ans2 = (last + last + m - 1) * m / 2 + 1;
        ans1 = max(ans1,ans2);
        int L = 1,R = m;
        while(L < R)
        {
            int mid1 = (R - L) / 3 + L;
            int mid2 = R - (R - L) / 3;
            if(check(mid1) <= check(mid2))L = mid1 + 1;
            else R = mid2 - 1;
        }
        ans1 = max(ans1,check(L));
        if(n == m)
        {
            cout << ans1 << '\n';
            continue ;
        }
        swap(n,m);
        L = 1,R = m;
        while(L < R)
        {
            int mid1 = (R - L) / 3 + L;
            int mid2 = R - (R - L) / 3;
            if(check(mid1) <= check(mid2))L = mid1 + 1;
            else R = mid2 - 1;
        }
        ans1 = max(ans1,check(L));
        cout << ans1 << '\n';
    }
}

T3

题目大意

原题

思路分析

由于我现在还不会,就先放一个链接在这里吧。

为啥原神这么对我。

T4

题目大意

你有一个长度为 \(n\) 的数列 \(a\),你现在要对其赋值,约束条件是对于每个 \(i < n\),\(a_i + a_{i + 1} \le c_i\)。

你还会有一个长度为 \(k\) 的序列 \(b\),对于每个 \(i = 1,2,.\!.\!.,n - k + 1\),\(j = i,i + 1,.\,.\,.,i + k - 1\) 都有贡献 \(b_i \times a_j\),最终答案即为所有贡献之和。

现在你需要知道以最优方案赋值 \(a_i\) 后贡献和的最大值,\(T\) 组数据。

\(1\le n \le 2 \times 10^6,1 \le k \le \left\lceil \frac{n}{2} \right\rceil,0 \le c_i,b_i \le 10^9,\sum n \le 4 \times 10^6\)

思路分析

注意到 \(n\) 开到这么大是想卡 \(\mathcal{O}(n \log n)\) 的。

但实际上我们只要稍微卡一下常就能通过了。

赛时 \(\mathcal{O}(n^2)\) 做法非常好想这里就不展示了。

直接来讲正解。

首先每个位置产生贡献的次数可以直接差分和前缀和 \(\mathcal{O}(n)\) 求出。

因为 \(k \le \left\lceil \dfrac{n}{2} \right\rceil\),所以第 \(k\) 个数产生的贡献一定是最大的,其实对于每个 \(b_i\) 都有贡献。

因此我们在想:直接让 \(a_k\) 这个位置取最大是不是就可以了呢?

不好意思,会被 hack 掉。

于是尝试打表找规律。发现一个很神奇的事情就是:答案是单峰的!!!也就是说:令 \(a_k = x\),答案为 \(y\),那么 \(y\) 是 \(x\) 的单峰函数。

所以我们做一遍三分即可。注意这题卡常,所以我后面的代码用二分来写,复杂度 \(\mathcal{O}(n \log n)\)。

所以这四道题有两道题有三分做法,但都不是正解。

#include <iostream>
#define int long long
namespace IO
{
	char Is[(1<<21)+10],Os[(1<<21)+10];
	int Ipt,Opt;
	char gc()
	{
		if(Ipt==1<<21)Ipt=0;
		if(!Ipt){Is[fread(Is,1,1<<21,stdin)]=0;}
		return Is[Ipt++];
	}
	void flush(){fwrite(Os,1,Opt,stdout);Opt=0;}
	void pc(char x)
	{
		if(Opt==1<<21)flush();
		Os[Opt++]=x;
	}
	int read()
	{
		int x=0;char ch=gc();while(ch<'0'||ch>'9')ch=gc();while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=gc();return x;
	}
}
using namespace IO;
using namespace std;
const int N = 2e6 + 5;
int n,k,a[N],b[N],c[N],id,T;
void write(__int128 xx)
{
    if(!xx)return ;
    write(xx / 10);
    cout << (int)(xx % 10);
}
__int128 check(int x)
{
    __int128 ans = 0;
    c[k] = x;
    for(int i = k + 1;i <= n;i ++)
        c[i] = min(a[i - 1] - c[i - 1],a[i]);
    for(int i = k - 1;i >= 1;i --)
        c[i] = min(a[i - 1],a[i] - c[i + 1]);
    for(int i = 1;i <= n;i ++)ans += (__int128)b[i] * c[i];
    return ans;
}
signed main()
{
    freopen("var.in","r",stdin);
    freopen("var.out","w",stdout);
    id = read(),T = read();
    while(T --)
    {
        n = read(),k = read();
        a[0] = a[n] = 2e9;
        int maxn = 0;
        for(int i = 1;i < n;i ++)a[i] = read();
        for(int i = 1;i <= n;i ++)b[i] = 0;
        for(int i = 1,x;i <= k;i ++)
        {
            x = read();
            b[i] += x,b[i + n - k + 1] -= x;
        }
        for(int i = 1;i <= n;i ++)b[i] += b[i - 1];
        int L = 0,R = min(a[k - 1],a[k]);
        while(L < R)
        {
            int mid = (L + R + 1) >> 1;
            if(check(mid) >= check(mid - 1))L = mid;
            else R = mid - 1;
        }
        write(check(L));
        putchar('\n');
    }
}
posted @ 2026-09-24 10:37  wuyixiang  阅读(9)  评论(0)    收藏  举报