20250426 NOI 模拟赛
20250426 NOI 模拟赛
Problem A. Varphi
由 \(\varphi(n)=n\prod_{p_i\in \mathbb{P}\land p_i\mid n}\frac{p_i-1}{p_i}\),得
证明比较简单,此处略去。但直接这样计算并没有优化复杂度。
考虑枚举 \(\gcd(x,y)\) 的约数 \(d\),有
设 \(\gcd(x,y)=g\)。我们可以看做 \(d\) 乘上若干质数 \(p_i\) 得到 \(g\)。
若 \(p_i\mid d\),\(\dfrac{d\times p_i}{\varphi(d\times p_i)}=\dfrac{d\times p_i}{\varphi(d)\times p_i}=\dfrac{d}{\varphi(d)}\)。
若 \(p_i\nmid d\),\(\dfrac{d\times p_i}{\varphi(d\times p_i)}=\dfrac{d\times p_i}{\varphi(d)\times (p_i-1)}> \dfrac{d}{\varphi(d)}\)
所以 \(\dfrac{d}{\varphi(d)}\) 在乘质数过程中单调不降。
所以这样正确的答案一定在 \(d=\gcd(x,y)\) 时取到。
我们对于每个 \(d\) 分别处理,把 \(d\mid A_i\) 的 \(i\) 全部拿出来放入集合 \(S_d\) 中。由于 \(A\) 是个排列,所以对于所有的 \(d\) 拿出 \(i\) 的总个数为 \(O(n\ln n)\)。
当 \(d\) 固定时,$\frac{d}{\varphi(d)} $ 也固定。那么对于每组询问 \([L,R]\),我们只需要找出 \([L,R]\cap S_d\) 中 \(\varphi(A_i)\) 的最大、次大值。
但若对于每个 \(d\) 都处理一遍所有询问,复杂度将达到 \(O(Qn\ln n)\)。所以我们考虑如何只处理一遍询问。
枚举 \(S_d\) 中的次大值 \(A_x\),那么对应的最大值就为其左边或右边第一个大于等于它的 \(A_y\)。正确性证明略去。
于是将 \((x,y,\varphi(A_x)\varphi(A_y)\frac{d}{\varphi(d)})\) 与询问一起做扫描线即可。
struct Node{
int x,y,id;
ll v;
bool operator<(const Node& tmp)const{
if(x!=tmp.x) return x>tmp.x;
else if(y!=tmp.y) return y<tmp.y;
else return id<tmp.id;
}
}c[N*35];
ll ans[N];
struct Fenwick{
ll tr[N];
void Update(int x,ll v){
for(;x<=n;x+=x&-x) Ckmax(tr[x],v);
}
ll Ask(int x){
ll res=0;
for(;x;x-=x&-x) Ckmax(res,tr[x]);
return res;
}
}Bit;
signed main(){
read(n); Init();
for(int i=1;i<=n;i++){
read(a[i]);
p[a[i]]=i;
}
for(int d=1;d<=n;d++){
vector<int> s;
for(int i=1;i*d<=n;i++) s.push_back(p[i*d]);
sort(s.begin(),s.end());
stack<int> t;
for(int i=0;i<(signed)s.size();i++){
while(t.size()&&phi[a[t.top()]]<phi[a[s[i]]]) t.pop();
if(t.size()) c[++m]={t.top(),s[i],0,1ll*phi[a[t.top()]]*phi[a[s[i]]]*d/phi[d]};
t.push(s[i]);
}
while(t.size()) t.pop();
for(int i=(signed)s.size()-1;i>=0;i--){
while(t.size()&&phi[a[t.top()]]<phi[a[s[i]]]) t.pop();
if(t.size()) c[++m]={s[i],t.top(),0,1ll*phi[a[t.top()]]*phi[a[s[i]]]*d/phi[d]};
t.push(s[i]);
}
}
read(Q);
for(int i=1;i<=Q;i++){
int l,r;
read(l),read(r);
c[++m]={l,r,i,0};
}
sort(c+1,c+m+1);
for(int i=1;i<=m;i++){
if(!c[i].id) Bit.Update(c[i].y,c[i].v);
else ans[c[i].id]=Bit.Ask(c[i].y);
}
for(int i=1;i<=Q;i++) printf("%lld\n",ans[i]);
return 0;
}
Problem B. 长条格子
\(O(nQ)\) Solution
设 \(f_i\) 表示从 \(i\) 开始先手是否必胜。
若 \([i+1,i+k]\) 中存在一个 \(j\) 使得 \(f_j=0\),则 \(f_i=1\);
否则,两个人都只能给 \(A_i\) 减一,于是 \(f_i=A_i \bmod 2\)。
对每次询问暴力修改、暴力 dp 即可。
没有修改操作
我们找到一个先手必败点 \(p\),其前面的一个先手必败点 \(q\) 一定是 \([1,i-k-1]\) 中最后一个 \(A_i \bmod 2=0\) 的点。
那么对于询问 \(s,t\),我们先找到 \(t\) 之前第一个先手必败点 \(z\)。根据前面的分析,\(z\) 一定是 \(t\) 前面第一个 \(A_i\bmod 2=0\) 的点。
然后我们从 \(z\) 开始,不断向前找必败点。那么从 \(s\) 开始先手必败的充要条件就是找的过程中经过了 \(s\)。
向前找的过程可以倍增优化,于是做到 \(O((n+Q)\log n)\) 的复杂度。
\(O(n \sqrt{n})\) Solution
题目中的信心没有很好的区间可加性,线段树不适用。所以我们选择更通用的分块。
对序列分块,设大小为 \(B\)。
对于每个位置 \(i\),我们维护出 \(nxt_i\),表示前面最近的一个且在块内的必败点位置,不存在则为 \(0\);再维护出 \(lst_i\),表示块内能跳到的最前面的位置,不存在则为 \(0\)。
对于某个位置 \(i\),考虑如何找到 \(i\) 前面第一个 \(A_j\bmod 2=0\) 的 \(j\)。
如果 \(nxt_i=0\),就令 \(i\) 为前面一个块的右端点,继续找;否则 \(nxt_i\) 就是我们想要的 \(j\)。
那么对于一次询问 \(s,t\),首先找到 \(t\) 前面第一个 \(A_z\bmod 2=0\) 的 \(z\),然后不断向前跳 \(lst\) (若 \(lst=0\) 则按上面方法跳)。这样每个块至多遍历一次,于是做到 \(O(B+\frac n B)\) 的查询复杂度。
对于修改操作,注意到我们只关注 $A_i $ 的奇偶性,所以区间加也就等价于区间取反。
为了支持修改,我们还要在每个位置上维护出取反后的 \(nxt,lst\)。对于整块修改,直接打标记;对于散块修改,直接暴力重构。于是做到 \(O(B+\frac n B)\) 的修改复杂度。
int n,K,Q,a[N],B,C;
int L[N],R[N],bl[N],nxt[N][2],lst[N][2],tag[N];
void Refresh(int x){
if(!tag[x]) return;
for(int i=L[x];i<=R[x];i++) a[i]^=1;
tag[x]=0;
}
void Build(int x){
Refresh(x);
for(int i=L[x];i<=R[x];i++){
nxt[i][0]=nxt[i][1]=0;
lst[i][0]=lst[i][1]=0;
if(i!=L[x]){
nxt[i][0]=nxt[i-1][0];
nxt[i][1]=nxt[i-1][1];
if(i-K-1>=L[x]){
lst[i][0]=lst[nxt[i-K-1][0]][0];
lst[i][1]=lst[nxt[i-K-1][1]][1];
if(!lst[i][0]) lst[i][0]=nxt[i-K-1][0];
if(!lst[i][1]) lst[i][1]=nxt[i-K-1][1];
}
}
nxt[i][a[i]&1]=i;
}
}
int Jump(int x){
if(x<=0) return x;
while(x&&!nxt[x][tag[bl[x]]]) x=R[bl[x]-1];
return nxt[x][tag[bl[x]]];
}
signed main(){
read(n),read(K),read(Q);
for(int i=1;i<=n;i++){
read(a[i]);
a[i]&=1;
}
B=sqrt(n);
for(int i=1;i<=n;i+=B){
L[++C]=i; R[C]=min(i+B-1,n);
for(int j=L[C];j<=R[C];j++) bl[j]=C;
}
for(int i=1;i<=C;i++) Build(i);
while(Q--){
int op; read(op);
if(op==1){
int l,r,d;
read(l),read(r),read(d);
if((d&1)^1) continue;
int p=bl[l],q=bl[r];
if(p==q){
for(int i=l;i<=r;i++) a[i]^=1;
Build(p); continue;
}
for(int i=p+1;i<=q-1;i++) tag[i]^=1;
for(int i=l;i<=R[p];i++) a[i]^=1;
for(int i=L[q];i<=r;i++) a[i]^=1;
Build(p); Build(q);
}
else{
int s,t; read(s),read(t);
t=Jump(t);
while(t>s){
if(lst[t][tag[bl[t]]]>=s) t=lst[t][tag[bl[t]]];
else t=Jump(t-K-1);
}
if(t==s) puts("B");
else puts("A");
}
}
return 0;
}

浙公网安备 33010602011771号