20250426 NOI 模拟赛

20250426 NOI 模拟赛

Link

Problem A. Varphi

\(\varphi(n)=n\prod_{p_i\in \mathbb{P}\land p_i\mid n}\frac{p_i-1}{p_i}\),得

\[\varphi(xy)=\varphi(x)\varphi(y)\dfrac{\gcd(x,y)}{\varphi(\gcd(x,y))} \]

证明比较简单,此处略去。但直接这样计算并没有优化复杂度。

考虑枚举 \(\gcd(x,y)\) 的约数 \(d\),有

\[\dfrac{\gcd(x,y)}{\varphi(\gcd(x,y))}\geq \dfrac{d}{\varphi(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;
}
posted @ 2025-04-29 21:24  XP3301_Pipi  阅读(22)  评论(0)    收藏  举报
Title