月赛题解
出题人给的理论最优开题顺序:G->HI->DFC->BE->A
AI给的顺序:

A. WA 了个WA
答案是全都不在 s 内的和 t 相同的子串数加只有尾部在 s 内的子串数加只有头部在 s 内的子串数加穿过 s 的子串数和 s 内的和 t 相同的子串数。
全都不在 s 内和都在 s 内可以直接算。
只有尾部在 s 内(只有头部在 s 内类似):
可以维护 s 前的字符串匹配 t 的最长前缀然后跳 fail 得到所有可以匹配的前缀。
对于 s 的开头可以维护匹配 t 的最长后缀。
可以发现所有相同长度的最长前缀答案是相同的,所以可以直接先预处理。
只有头部在 s 内和只有尾部在 s 内基本相同。
\(len(s)>len(t)\) 时没有穿过 s 的答案。
穿过 s 的答案:
对于一个 s 前的前缀 a 与一个 s 后的后缀 b,只有它们的长度之和是 \(len(t)-len(s)\) 并且 \(t(len(a)+1,len(a)+len(s))=s\) 才可以形成一个答案。
建出正向的 fail 树和反向的 fail 树。
可以发现一个 s 前的前缀 a 与一个 s 后的后缀 b的答案就是正向 fail 树的节点 a 的所有祖先和反向 fail 树的节点 b 所有祖先的可行组合的个数。
可以离线下来,dfs 正向的 fail 树,跑到某个节点时维护它的所有正向树祖先的可行后缀,查询某个后缀时就是查询这个后缀的所有反向树祖先的和,于是直接子树加单点查就好了。
\(O(n+qlogn)\)
不知道用一个串的所有 border 可以分成小于 log 个等差数列再等差数列求交可不可以做。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s1[1000010],s2[1000010],s3[1000010],s[1000010];
int t1[1000010],t2[1000010],sz[1000010],p1,p2,n,m,ipx;
basic_string<int>e1[1000010],e2[1000010];int q,ln;
struct sd{int p,v;};basic_string<sd>c[1000010];
int ip[1000010],t[1000010],res[1000010];bool can[1000010];
int hd[1000010],tl[1000010],cs1[1000010],cs2[1000010];
int ans1[1000010],ans2[10000010],to[1000010],ans;
void ad(int p,int x){for(;p<=ipx;p+=p&-p)t[p]+=x;}
void add(int l,int r,int x){ad(l,x),ad(r+1,-x);}
int qry(int p){int ret=0;for(;p;p-=p&-p)ret+=t[p];return ret;}
void dfs(int p){
ip[p]=++ipx,sz[p]=1;
for(int x:e2[p])dfs(x),sz[p]+=sz[x];
}
void cfs(int p){
if(can[p]&&p&&p<ln)add(ip[ln-p],ip[ln-p]+sz[ln-p]-1,1);
for(int x:e1[p])cfs(x);
for(sd x:c[p])res[x.p]+=qry(ip[x.v]);
if(can[p]&&p&&p<ln)add(ip[ln-p],ip[ln-p]+sz[ln-p]-1,-1);
}
int main(){
scanf("%s%s%d",s2+1,s1+1,&q);
n=strlen(s1+1),m=strlen(s2+1),ln=n-m;
for(int i=1;i<=n;i++)s3[i]=s1[n-i+1];
for(int i=2,j=0;i<=n;i++){
while(j&&s1[j+1]!=s1[i])j=t1[j];
if(s1[j+1]==s1[i])j++;t1[i]=j;
}
for(int i=1,j=0;i<=m;i++){
while(j&&s1[j+1]!=s2[i])j=t1[j];
if(s1[j+1]==s2[i])j++;if(j==n)j=t1[j],ans++;
if(i==m)p2=j;
}
for(int i=2,j=0;i<=m;i++){
while(j&&s2[j+1]!=s2[i])j=to[j];
if(s2[j+1]==s2[i])j++;to[i]=j;
}
for(int i=1,j=0;i<=n;i++){
while(j&&s2[j+1]!=s1[i])j=to[j];
if(s2[j+1]==s1[i])j++;
if(j==m)j=to[j],can[i-m]=true;
}
for(int i=2,j=0;i<=n;i++){
while(j&&s3[j+1]!=s3[i])j=t2[j];
if(s3[j+1]==s3[i])j++;t2[i]=j;
}
for(int i=m,j=0;i;i--){
while(j&&s3[j+1]!=s2[i])j=t2[j];
if(s3[j+1]==s2[i])j++;if(j==n)j=t2[j];
if(i==1)p1=j;
}
while(p2)cs2[n-p2]++,p2=t1[p2];while(p1)cs1[n-p1]++,p1=t2[p1];
for(int i=1;i<=n;i++)cs2[i]+=cs2[t2[i]],cs1[i]+=cs1[t1[i]];
for(int i=1;i<=n;i++)e1[t1[i]]+=i,e2[t2[i]]+=i;
for(int _=1,a,b;_<=q;_++){
scanf("%d",&a);
hd[_]=hd[_-1],tl[_]=tl[_-1];
ans1[_]=ans1[_-1],ans2[_]=ans2[_-1];
if(a==1||a==2){
scanf("%s",s+1),b=strlen(s+1);
if(a==1){
for(int i=1,j=hd[_];i<=b;i++){
while(j&&s1[j+1]!=s[i])j=t1[j];
if(s1[j+1]==s[i])j++;
if(j==n)j=t1[j],ans1[_]++;
if(i==b)hd[_]=j;
}
}else{
for(int i=b,j=tl[_];i;i--){
while(j&&s3[j+1]!=s[i])j=t2[j];
if(s3[j+1]==s[i])j++;
if(j==n)j=t2[j],ans2[_]++;
if(i==1)tl[_]=j;
}
}
}else{
scanf("%d",&b);
if(a==3)hd[_]=hd[b],ans1[_]=ans1[b];
else tl[_]=tl[b],ans2[_]=ans2[b];
}
res[_]=ans1[_]+ans2[_]+cs1[hd[_]]+cs2[tl[_]],c[hd[_]]+=(sd){_,tl[_]};
}
if(ln>0)dfs(0),cfs(0);
for(int i=1;i<=q;i++)printf("%d\n",res[i]+ans);
return 0;
}
B. 神代共鸣
题意简述
定义序列 \(E(n)\):
有 \(T\) 组询问,每组给定 \(P, Q, n, m\),求 \(\gcd(E(n), E(m)) \bmod 998244353\)。
关键性质
对于形如 \(E(n) = P \cdot E(n-1) + Q \cdot E(n-2)\) 的线性递推数列,有重要性质:
证明思路:这类递推数列与矩阵幂运算密切相关,可以通过矩阵形式的欧几里得算法证明。类似斐波那契数列的 \(\gcd(F_n, F_m) = F_{\gcd(n,m)}\) 的性质可以推广到更一般的二阶线性递推(当系数满足一定条件时)。
算法步骤
- 对于每组询问,计算 \(g = \gcd(n, m)\)
- 计算 \(E(g) \bmod 998244353\)
- 输出结果
矩阵快速幂求 \(E(k)\)
递推式可以写成矩阵形式:
因此:
特别地,\(E(k)\) 就是 \(M^{k-1}\) 的左上角元素(当 \(k \ge 1\) 时)。
时间复杂度
- 每次询问需要 \(O(\log \min(n, m))\) 的矩阵快速幂
- 总复杂度 \(O(T \log \max(n, m))\),可以轻松通过
代码实现
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
const int MOD = 998244353;
struct Matrix {
int a[2][2];
Matrix() { memset(a, 0, sizeof a); }
Matrix operator*(const Matrix& o) const {
Matrix r;
for (int i = 0; i < 2; ++i)
for (int k = 0; k < 2; ++k)
for (int j = 0; j < 2; ++j)
r.a[i][j] = (r.a[i][j] + (int64)a[i][k] * o.a[k][j]) % MOD;
return r;
}
};
int calcE(int64 k, int P, int Q) {
// 计算 E(k) mod MOD, 其中 E(1)=1, E(0)=0
// 使用矩阵快速幂: M = [[P, Q], [1, 0]]
// E(k) = (M^{k-1})_{0,0}
if (k == 0) return 0;
if (k == 1) return 1 % MOD;
Matrix res, base;
res.a[0][0] = res.a[1][1] = 1; // 单位矩阵
base.a[0][0] = P % MOD;
base.a[0][1] = Q % MOD;
base.a[1][0] = 1;
base.a[1][1] = 0;
int64 exp = k - 1;
while (exp) {
if (exp & 1) res = res * base;
base = base * base;
exp >>= 1;
}
return res.a[0][0]; // 第一行第一列
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int P, Q;
int64 n, m;
cin >> P >> Q >> n >> m;
int64 g = __gcd(n, m);
int ans = calcE(g, P, Q);
cout << ans << '\n';
}
return 0;
}
E. 时空炼金阵
题意简述
有 \(n\) 个能量节点,初始每个节点的能量值为 \(1\)。定义斐波那契数列:
对于 \(i \le 0\),扩展定义为 \(F_i = F_{i+2} - F_{i+1}\)(等价于 \(F_0 = 0,\ F_{-1} = 1,\ F_{-2} = -1\) 等)。
需要维护三种操作:
- 区间加:\([l, r]\) 内每个节点的能量值增加 \(k\)
- 区间赋值:\([l, r]\) 内每个节点的能量值设为 \(x\)
- 区间查询:询问 \(\sum_{i=l}^r F_{a_i} \bmod (10^9+7)\)
关键观察
斐波那契数列满足线性递推。对于区间加操作 \(a_i \leftarrow a_i + k\),我们需要快速从 \(F_{a_i}\) 转移到 \(F_{a_i + k}\)。
斐波那契的矩阵表示
定义转移矩阵:
则有:
更常用的形式:
由此可得:
处理负指数
对于负整数 \(k\),我们需要 \(M^k\) 的定义。由于 \(M\) 可逆,\(M^{-1}\) 存在:
实际上 \(M^{-k} = (M^{-1})^k\)。
因此对于任意整数 \(k\)(正或负),我们可以用矩阵快速幂计算 \(M^k\)。
数据结构设计
我们需要维护每个位置的矩阵值而非直接的数值。设每个位置维护一个矩阵 \(T_i\),使得:
更简洁地,我们可以直接维护矩阵 \(\begin{bmatrix} F_{a_i+1} & F_{a_i} \\ F_{a_i} & F_{a_i-1} \end{bmatrix} = M^{a_i}\)。
区间加
当 \(a_i\) 变为 \(a_i + k\) 时:
因此区间加就是区间左乘矩阵 \(M^k\)。
区间赋值
当 \(a_i\) 变为 \(x\) 时:
因此区间赋值就是区间替换为常数矩阵 \(M^x\)。
区间查询
我们需要 \(\sum F_{a_i}\)。注意 \(M^{a_i}\) 的右上角元素就是 \(F_{a_i}\),因此我们只需要维护每个区间元素 \(M^{a_i}\) 的矩阵和,查询时取和的右上角元素。
懒标记设计
由于有两种操作,我们需要两个懒标记:
- 乘法标记 \(mul\):表示区间需要左乘的矩阵(对应区间加)
- 赋值标记 \(set\):表示区间被赋值为某个常数矩阵(优先级高于乘法标记)
下传时:先处理赋值标记,再处理乘法标记。
矩阵快速幂预处理
因为 \(|k|, |x| \le 10^9\),不能每次都用快速幂。注意到只有 \(O(m)\) 次操作,可以预处理所有需要的 \(M^k\) 和 \(M^{-k}\)。但代码中采用的方法是每次操作时用快速幂计算,由于 \(m \le 2 \times 10^5\),\(O(m \log |k|)\) 可行。
时间复杂度
- 线段树区间操作 \(O(\log n)\)
- 每次操作需要一次矩阵快速幂 \(O(\log |k|)\)
- 总复杂度 \(O((n + m) \log n \log \max(|k|, |x|))\)
代码实现
#include<iostream>
#include<cstdio>
using namespace std;
#define long long long
const int mod=1e9+7;
struct sd{
long a,b,c,d;
sd(int a=0,int b=0,int c=0,int d=0):a(a),b(b),c(c),d(d){}
friend sd operator *(const sd &x,const sd &y){
return sd((x.a*y.a+x.b*y.c)%mod, (x.a*y.b+x.b*y.d)%mod,
(x.c*y.a+x.d*y.c)%mod, (x.c*y.b+x.d*y.d)%mod);
}
bool operator !=(const sd &x){
return a!=x.a || b!=x.b || c!=x.c || d!=x.d;
}
}f1(0,1,1,1), f2(mod-1,1,1,0), t[1000010], lz[1000010], is[1000010];
int n,m;
// 计算 M^k,其中 M = [[1,1],[1,0]]
// k > 0 时用 f1 = M,k < 0 时用 f2 = M^{-1}
sd get(int x){
sd ret(1,0,0,1), g = (x > 0 ? f1 : f2);
if(x < 0) x = -x;
for(; x; x >>= 1, g = g * g)
if(x & 1) ret = ret * g;
return ret;
}
// 建树:每个位置初始值为 M^1(因为 a_i = 1)
void bld(int p, int l, int r){
lz[p] = sd(1,0,0,1);
if(l == r){
t[p] = sd(1,1,0,0); // M^1 的矩阵形式
return;
}
int mid = (l + r) >> 1;
bld(p<<1, l, mid);
bld(p<<1|1, mid+1, r);
t[p] = sd((t[p<<1].a + t[p<<1|1].a) % mod,
(t[p<<1].b + t[p<<1|1].b) % mod, 0, 0);
}
// 下传懒标记
void down(int p, int l, int r){
int mid = (l + r) >> 1;
// 先处理乘法标记
t[p<<1] = t[p<<1] * lz[p];
lz[p<<1] = lz[p<<1] * lz[p];
is[p<<1] = is[p<<1] * lz[p];
t[p<<1|1] = t[p<<1|1] * lz[p];
lz[p<<1|1] = lz[p<<1|1] * lz[p];
is[p<<1|1] = is[p<<1|1] * lz[p];
lz[p] = sd(1,0,0,1);
// 再处理赋值标记
if(is[p] != sd()){
int lenL = mid - l + 1, lenR = r - mid;
t[p<<1] = sd(is[p].a * lenL % mod, is[p].b * lenL % mod, 0, 0);
t[p<<1|1] = sd(is[p].a * lenR % mod, is[p].b * lenR % mod, 0, 0);
is[p<<1] = is[p<<1|1] = is[p];
lz[p<<1] = lz[p<<1|1] = sd(1,0,0,1);
is[p] = sd();
}
}
// 区间加(左乘矩阵 x)
void add(int p, int l, int r, int i, int j, sd x){
if(i <= l && r <= j){
t[p] = t[p] * x;
lz[p] = lz[p] * x;
is[p] = is[p] * x;
return;
}
if(lz[p] != sd(1,0,0,1) || is[p] != sd()) down(p, l, r);
int mid = (l + r) >> 1;
if(i <= mid) add(p<<1, l, mid, i, j, x);
if(j > mid) add(p<<1|1, mid+1, r, i, j, x);
t[p] = sd((t[p<<1].a + t[p<<1|1].a) % mod,
(t[p<<1].b + t[p<<1|1].b) % mod, 0, 0);
}
// 区间赋值(设为常数矩阵 x)
void mak(int p, int l, int r, int i, int j, sd x){
if(i <= l && r <= j){
t[p] = sd(x.a * (r - l + 1) % mod, x.b * (r - l + 1) % mod, 0, 0);
lz[p] = sd(1,0,0,1);
is[p] = x;
return;
}
if(lz[p] != sd(1,0,0,1) || is[p] != sd()) down(p, l, r);
int mid = (l + r) >> 1;
if(i <= mid) mak(p<<1, l, mid, i, j, x);
if(j > mid) mak(p<<1|1, mid+1, r, i, j, x);
t[p] = sd((t[p<<1].a + t[p<<1|1].a) % mod,
(t[p<<1].b + t[p<<1|1].b) % mod, 0, 0);
}
// 区间查询(返回 ΣF_{a_i})
long qry(int p, int l, int r, int i, int j){
if(i <= l && r <= j) return t[p].a; // t[p].a 是 ΣF_{a_i+1},待确认
if(lz[p] != sd(1,0,0,1) || is[p] != sd()) down(p, l, r);
int mid = (l + r) >> 1;
long ret = 0;
if(i <= mid) ret += qry(p<<1, l, mid, i, j);
if(j > mid) ret += qry(p<<1|1, mid+1, r, i, j);
return ret % mod;
}
int main(){
cin >> n >> m;
bld(1, 1, n);
for(int a, b, c; m--;){
cin >> a;
if(a == 1){
cin >> a >> b >> c;
add(1, 1, n, a, b, get(c)); // 区间加 c,左乘 M^c
}
else if(a == 2){
cin >> a >> b >> c;
// 区间赋值为 c,即 M^c = M^{c-1} * M^1
mak(1, 1, n, a, b, sd(1,1,0,0) * get(c-1));
}
else{
cin >> a >> b;
cout << qry(1, 1, n, a, b) << "\n";
}
}
return 0;
}
G. 虚空裂隙·灵魂共振协议
题意简述
你有初始灵能值 \(x\),依次经过 \(n\) 个节点,每个节点有阈值 \(d_i\)。规则如下:
- 若当前灵能值 \(E > d_i\),则 \(E \leftarrow E + 1\)
- 若当前灵能值 \(E \le d_i\),则 \(E \leftarrow E - 1\)
求经过所有节点后的最终灵能值。
题目分析
这是一道非常直接的模拟题。题目描述虽然包装了复杂的背景,但核心逻辑就是上述两条简单的规则。
由于 \(n \le 10^5\),直接模拟的时间复杂度为 \(O(n)\),完全可行。
算法步骤
- 读入 \(n\) 和初始值 \(x\)
- 初始化当前灵能值
cur = x - 循环 \(n\) 次,读入每个 \(d_i\):
- 如果
cur > d[i],则cur++ - 否则
cur--
- 如果
- 输出最终的
cur
时间复杂度
- \(O(n)\),可以轻松通过
空间复杂度
- \(O(1)\) 额外空间(不需要存储所有 \(d_i\),可以边读边处理)
代码实现
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
long long x;
cin >> n >> x;
long long cur = x;
for (int i = 0; i < n; i++) {
long long d;
cin >> d;
if (cur > d) {
cur++;
} else {
cur--;
}
}
cout << cur << endl;
return 0;
}
H. htd1 的 mex 问题
题意简述
给定一个包含 \(n\) 个元素的多重集合(元素为非负整数),可以将其拆分成任意个非空子集,使得所有子集的 mex 值之和最大。求这个最大值。
题目分析
mex 的定义
对于一个集合 \(S\),\(\text{mex}(S)\) 表示没有在 \(S\) 中出现过的最小非负整数。
例如:\(\text{mex}(\{1,2\}) = 0\),\(\text{mex}(\{0,1,3\}) = 2\)。
关键思路
设原多重集合中数字 \(i\) 出现的次数为 \(\text{cnt}[i]\)。
考虑如何分配这些数字到各个子集中,以最大化 mex 之和。
核心观察:
- 一个子集的 mex 值为 \(k\),意味着该子集必须包含 \(0,1,2,\dots,k-1\) 各至少一次,且不能包含 \(k\)。
- 为了最大化 mex 之和,每个子集的 mex 值应当尽量大,但同时受到数字出现的次数限制。
贪心构造
我们可以将问题转化为:
设有 \(m\) 个子集,每个子集的 mex 值至少为多少?
假设我们想要得到 \(t\) 个 mex 值 \(\ge 1\) 的子集,那么我们需要至少有 \(t\) 个 \(0\)(因为每个 mex \(\ge 1\) 的子集必须包含 \(0\))。
同理,想要得到 \(t\) 个 mex 值 \(\ge 2\) 的子集,需要至少有 \(t\) 个 \(0\) 和 \(t\) 个 \(1\)。
更一般地,想要得到 \(t\) 个 mex 值 \(\ge k\) 的子集,需要对于每个 \(i \in [0, k-1]\),都有 \(\text{cnt}[i] \ge t\)。
转化为数值计算
设 \(f(k)\) 表示最多能有多少个子集的 mex 值 \(\ge k\)。则:
因为要同时满足前 \(k\) 个数字的个数都不少于该数量。
那么,mex 值恰好等于 \(k\) 的子集数量为:
因为 mex \(\ge k\) 的子集数量减去 mex \(\ge k+1\) 的子集数量,就是 mex 恰好为 \(k\) 的子集数量。
于是 mex 值之和为:
因为:
最终公式
设 \(g(i) = \min_{0 \le j \le i} \text{cnt}[j]\),则:
其中 \(g(-1)\) 定义为无穷大,实际只需要计算到某个上界。
由于 \(a_i \le 1000\),我们只需要考虑 \(i\) 从 \(0\) 到 \(1001\) 即可(因为超过 \(\max(a_i)+1\) 后 \(\text{cnt}[i]=0\),\(g(i)\) 就会变成 \(0\))。
算法步骤
- 统计每个数字的出现次数 \(\text{cnt}[i]\)(\(0 \le i \le 1001\))
- 初始化
min_cnt = cnt[0] - 初始化答案
ans = 0 - 从 \(i = 1\) 开始循环到 \(1001\):
- 先将当前的
min_cnt累加到答案(对应公式中的 \(g(i-1)\)) - 更新
min_cnt = min(min_cnt, cnt[i])
- 先将当前的
- 输出答案
时间复杂度
- \(O(\max(a_i) + n)\),本题中 \(\max(a_i) \le 1000\),非常快
代码实现
// htd1_mex_simple.cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> cnt(n + 5, 0);
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x <= n) { // 大于 n 的数字对 mex 没有贡献(因为最多只有 n 个数,mex 不会超过 n)
cnt[x]++;
}
}
long long ans = 0;
int min_cnt = cnt[0]; // min(c0, c1, ..., c{i-1})
for (int i = 1; i <= n + 1; i++) {
ans += min_cnt; // 累加 g(i-1) = min_{0<=j<i} cnt[j]
min_cnt = min(min_cnt, cnt[i]);
}
cout << ans << endl;
return 0;
}
I. Distinct Prefix Sums Modulo n 题解
证明
设排列 \(p_1, p_2, \ldots, p_n\),前缀和 \(S_k = \sum_{i=1}^k p_i\),\(s_k = S_k \bmod n\)。
引理:\(S_n = \frac{n(n+1)}{2}\),因此:
- 当 \(n\) 为奇数时,\(S_n \equiv 0 \pmod{n}\)
- 当 \(n\) 为偶数时,\(S_n \equiv \frac{n}{2} \pmod{n}\)
情况一:\(n\) 为奇数且 \(n > 1\)
-
计算总和
\[S_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}. \]因为 \(n\) 是奇数,\(\frac{n+1}{2}\) 为整数,所以 \(S_n \equiv 0 \pmod n\)。
于是 \(S_n \bmod n = 0\),且由要求知 \(S_1,\dots,S_{n-1} \bmod n\) 恰好是 \(\{1,2,\dots,n-1\}\)(不含 \(0\))。 -
确定 \(p_n\)
因前 \(n-1\) 个余数已用尽 \(1,\dots,n-1\),必有 \(S_{n-1} \equiv n-1 \pmod n\)。
故\[p_n = S_n - S_{n-1} \equiv 0 - (n-1) \equiv 1 \pmod n, \]而 \(1 \le p_n \le n\),所以 \(p_n = 1\)。
-
反向递推
已知 \(p_n = 1\),且 \(S_{n-1} \equiv n-1 \pmod n\)。
对 \(k = n-1, n-2, \dots, 2\) 依次使用\[p_k = S_k - S_{k-1} \equiv S_k - S_{k-1} \pmod n, \]结合余数集合唯一性,可唯一推出
\[p_{n-1} = 2,\; p_{n-2} = 3,\; \dots,\; p_1 = n. \] -
矛盾
此时 \(S_1 = p_1 = n \equiv 0 \pmod n\),与 \(S_1 \bmod n \neq 0\)(因为 \(0\) 仅由 \(S_n\) 取到)矛盾。
因此假设不成立,对任意奇数 \(n>1\),这样的排列不存在。 \(\square\)
情况二:\(n = 1\)
排列 \([1]\),前缀和 \(S_1 = 1 \equiv 0 \pmod{1}\),满足条件。
情况三:\(n\) 为偶数
构造排列如下:
即:\(1, n, 2, n-1, 3, n-2, \ldots, \frac{n}{2}, \frac{n}{2}+1\)。
可以验证前缀和模 \(n\) 两两不同,恰好构成 \(0, 1, \ldots, n-1\)。
结论:当且仅当 \(n\) 为偶数或 \(n = 1\) 时存在解。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
if (n % 2 == 1 && n != 1) {
cout << "NO\n";
}
else{
cout << "YES\n";
}
return 0;
}

浙公网安备 33010602011771号