「小Z的排名」题解
T3「小Z的排名」题解
题意简述
有一个 \(n\) 个点的图,点从 \(1\) 到 \(n\) 编号。对于 \(u \lt v\),若 \(v\) 是 \(u\) 的倍数则连边。每次询问给定 \(n\) 和 \(k\),求前 \(n\) 个点中度数的第 \(k\) 小值。
度数公式
对于点 \(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\) 小:
- 先扫块,找到第 \(k\) 小落在哪个块(\(O(\sqrt{n})\));
- 再扫块内,找到具体度数(\(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();
}

浙公网安备 33010602011771号