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');
}
}

浙公网安备 33010602011771号