「小Z的排名」题解

T3「小Z的排名」题解

题意简述

有一个 \(n\) 个点的图,点从 \(1\)\(n\) 编号。对于 \(u \lt v\),若 \(v\)\(u\) 的倍数则连边。每次询问给定 \(n\)\(k\),求前 \(n\) 个点中度数的第 \(k\) 小值。

NOIP8-T3 小 Z 的排名_-ACGO


度数公式

对于点 \(u\),它的度数由两部分构成:

  • \(u\) 小的约数:共有 \(d(u) - 1\) 个(\(d(u)\)\(u\) 的约数个数,减去 \(u\) 自身);
  • \(u\) 大的倍数\(\le n\) 的倍数有 \(\lfloor n/u \rfloor\) 个,减去 \(u\) 自身,共 \(\lfloor n/u \rfloor - 1\) 个。

因此:

\[\deg(u) = d(u) + \left\lfloor\frac{n}{u}\right\rfloor - 2 \]


增量维护

\(n\) 增大到 \(n+1\) 时:

  • 新点 \(n+1\) 的度数为 \(\deg(n+1) = d(n+1) - 1\)(此时没有比它大的倍数);
  • 对于 \(n+1\) 的每个约数 \(v\)\(v \lt n+1\)),\(\lfloor (n+1)/v \rfloor = \lfloor n/v \rfloor + 1\),所以 \(\deg(v)\) 增加 \(1\)

因此可以把所有询问按 \(n\) 排序,从小到大增量处理。每次 \(n\) 增加 \(1\),更新 \(n\) 的所有约数对应的度数,并维护度数分布。


分块求第 \(k\)

度数值域最大约为 \(n\),直接用桶维护每个度数的出现次数 \(cnt[x]\)

将值域按块大小 \(B = \sqrt{n}\) 分块,\(block[id]\) 记录第 \(id\) 块内所有度数的出现次数之和。

加入度数 \(x\)cnt[x]++block[x/B]++

删除度数 \(x\)cnt[x]--block[x/B]--

查询第 \(k\)

  1. 先扫块,找到第 \(k\) 小落在哪个块(\(O(\sqrt{n})\));
  2. 再扫块内,找到具体度数(\(O(B) = O(\sqrt{n})\))。

单次查询 \(O(\sqrt{n})\)


线性筛预处理

用线性筛预处理每个数的最小质因数 \(spf\),用于快速求约数列表和约数个数:

  • \(d(1) = 1\)
  • 对于 \(i \gt 1\),对 \(spf[i]\) 的幂次分解,\(d(i) = \prod (c+1)\)

枚举约数时,利用 \(spf\) 分解质因数,逐层生成所有约数。


复杂度分析

步骤 复杂度
线性筛 \(spf\) \(O(n \log \log n)\)
求所有 \(d(i)\) \(O(n \log n)\)
枚举所有约数(调和级数) \(O(n \log n)\)
单次度数更新 \(O(1)\)
单次查询 \(O(\sqrt{n})\)
总复杂度 \(O(n \log n + T \sqrt{n})\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=8000005;
const int B=1000;

int d[maxn];
int spf[maxn];
int deg[maxn];
int cnt[maxn];
int block[maxn/B+5];

struct Query {
	int n,k,id;
	bool operator < (const Query &b) const {
		return n<b.n;
	}
};

void add_degree(int x) {
	cnt[x]++;
	block[x/B]++;
}

void remove_degree(int x) {
	cnt[x]--;
	block[x/B]--;
}

int query_kth(int k) {
	int blk=0;
	while (block[blk]<k) {
		k-=block[blk];
		blk++;
	}
	for (int i=blk*B;;i++) {
		k-=cnt[i];
		if (k<=0) return i;
	}
}

void get_divisors(int n,vector<int> &divs) {
	divs.clear();
	if (n==1) {
		divs.push_back(1);
		return ;
	}
	divs.push_back(1);
	int temp=n;
	while (temp>1) {
		int p=spf[temp];
		int c=0;
		while (temp%p==0) {
			temp/=p;
			c++;
		}
		int sz=divs.size();
		int mul=1;
		for (int i=0;i<c;i++) {
			mul*=p;
			for (int j=0;j<sz;j++) {
				divs.push_back(divs[j]*mul);
			}
		}
	}
}

struct GenHelper{
	std::mt19937 Rd;
	int N,K;
	void srand(int _S,int _N,int _K){Rd.seed(_S),N=_N,K=_K;}
	int read(){int a=Rd();if(a<0)a=-a;return a;}
	std::pair<int,int> get_nk(){
		int n=read()%N+1,k=read()%(K<n?K:n)+1;
		return std::pair<int,int>(n,k);
	}
}Gen;

void solve() {
	int S,T,N,K;
	cin>>S>>T>>N>>K;
	Gen.srand(S,N,K);
	vector<Query> queries;
	int max_n=0;
	for (int i=0;i<T;i++) {
		Query q;
		std::pair<int,int> nk=Gen.get_nk();
		q.n=nk.first,q.k=nk.second;
		q.id=i;
		queries.push_back(q);
		max_n=max(max_n,q.n);
	}
	
	for (int i=2;i<=max_n;i++) {
		if (!spf[i]) {
			spf[i]=i;
			if ((ll)i*i<=max_n) {
				for (int j=i*i;j<=max_n;j+=i) {
					if (!spf[j]) spf[j]=i;
				}
			}
		}
	}
	
	d[1]=1;
	for (int i=2;i<=max_n;i++) {
		int temp=i;
		d[i]=1;
		while (temp>1) {
			int p=spf[temp];
			int c=0;
			while (temp%p==0) {
				temp/=p;
				c++;
			}
			d[i]*=(c+1);
		}
	}
	
	sort(queries.begin(),queries.end());
	int qi=0;
	vector<int> ans(T);
	vector<int> divs;
	
	for (int n=1;n<=max_n;n++) {
		get_divisors(n,divs);
		for (int i=0;i<(int)divs.size();i++) {
			int v=divs[i];
			if (v<n) {
				remove_degree(deg[v]);
				deg[v]++;
				add_degree(deg[v]);
			}
		}
		
		deg[n]=d[n]-1;
		add_degree(deg[n]);
		
		while (qi<T && queries[qi].n==n) {
			ans[queries[qi].id]=query_kth(queries[qi].k);
			qi++;
		}
	}
	
	ll res=0;
	for (int i=0;i<T;i++) res^=ans[i];
	cout<<res<<'\n';
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	solve();
}
posted @ 2026-08-20 19:53  acceptedK77  阅读(24)  评论(1)    收藏  举报