题解:P16392 [IATI 2024] Lex_gcd

· · 题解

题意简述

给定一个正整数序列。 可以选择一个元素乘以质数 p,也可以不操作。

随后任意排列序列。 要求任意 K 个位置上的最大公约数都保持不变。

求字典序最小的可行序列。

解题思路

先不进行乘法操作。

固定质数 q。 记 e_i(q)a_iq 的指数, 记 t_q 为全部指数中的第 K 大值。

对固定位置 i,考虑所有包含它的 K 元集合。 这些集合的最小指数所能达到的最大值为:

\max_{\substack{i\in S\\|S|=K}}\min_{j\in S}e_j(q)=\min(e_i(q),t_q)

这是因为指数严格大于 t_q 的位置至多有 K-1 个。 而指数不小于 t_q 的位置至少有 K 个。

所以,全部 K 元集合的最大公约数, 能够确定每个位置的截断指数。

反过来,任取 K 个位置。 其中至少有一个位置的指数不超过 t_q。 因此,截断不会改变这 K 个指数的最小值。

于是,两种排列等价, 当且仅当每个位置的全部截断指数相同。

定义位置 i 的特征值:

h_i=\prod_q q^{\min(e_i(q),t_q)}

只有特征值相同的两个位置可以交换元素。 把相同特征值的位置分成一组。 组内位置升序排列,数值也升序排列, 再按顺序一一对应。

每个组都这样放置, 就得到不进行乘法时的字典序最小序列。 记这个序列为基准序列。

下面分析把 a_i 乘以 p 的影响。 只有质数 p 的指数可能改变。

e_i(p)\ge t_p, 旧特征值不会直接改变。 第 K 大指数可能保持不变,也可能增加。

若阈值不变,新的分组与旧分组相同。 若阈值增加,新分组只是旧分组的进一步细分。

在任一情况下, 把结果中的 pa_i 换回 a_i, 都会得到一个旧问题的合法排列, 且这个排列严格更小。

基准序列已经是旧问题的最小排列。 所以,这些操作不可能改进答案。

唯一需要考虑的是:

e_i(p)<t_p

此时阈值保持不变, 位置 i 的特征值会从 h_i 变成 ph_i。 也就是说,只把一个位置和一个数值移到另一个组。

若特征值 ph_i 的组原本不存在, 新组只包含位置 i 与数值 pa_i

i 在原组中的位置排名为 r

若 $s<r$,删除后更早的位置会得到更大的数。 若 $s=r$,位置 $i$ 本身会变大。 若 $s>r$,位置 $i$ 仍先于可能变小的位置, 而它得到的 $pa_i$ 大于基准值。 所以,目标组原本不存在时也无法改进答案。 现在只需枚举满足条件的原组, 且目标组 $ph_i$ 已经存在的元素。 对每个特征组,同时保存: - 按数值排序的二元组 - 按位置排序的下标序列 从原组删除位置 $i$ 与数值 $a_i$ 时, 只有两个排名之间的一段会移动一位。 向目标组插入位置 $i$ 与数值 $pa_i$ 时同理。 两个移动区间的端点都能通过二分求出。 字典序只取决于最早发生变化的位置。 每段移动只需记录它的第一个新值。 加上位置 $i$ 自身, 一个候选至多需要记录三个变化点。 代码用固定长度的 `Change` 保存这些变化。 比较两个候选时, 按位置合并变化点,未记录的位置读取基准值。 第一次出现不同数值即可决定字典序。 相同的 $a_i$ 可能对应多个下标。 第一轮先确定最优的原组与被乘数数值。 再删除一份该数值,构造临时基准序列。 第二轮只重新枚举这个原组的下标。 这样可以精确处理相等数值, 不需要随机截断候选集合。 最后,将选中的 $a_i$ 乘以 $p$, 重新构造一次特征组与答案。 特征值构造需要分解所有数。 相同数值的质因数分解完全相同。 代码按数值缓存分解结果, 第二次构造时也会复用原有缓存。 若 $p\ge40000$,修改后的数可能超过 $10^9$。 先除去质因子 $p$, 剩余部分仍不超过原数上界。 之后试除所有小于 $40000$ 的质数即可。 除质因数分解外, 排序、分组与候选比较需要 $O(n\log n)$ 时间。 空间复杂度为 $O(n\log A)$, 其中 $A$ 是输入数值上界。 ## 参考代码 ```cpp #include <bits/stdc++.h> using namespace std; using ll=long long; using pli=pair<ll,int>; using pil=pair<int,ll>; const int V=40000; struct Group { vector<pli> val; vector<int> pos; }; struct Change { pil a[3]; int n; }; using Groups=map<ll,Group>; int n,k; ll p; bool vis[V]; vector<int> pri; vector<ll> a; map<ll,vector<pli>> cache; void init() { for(int i=2;i<V;i++) { if(vis[i])continue; pri.push_back(i); for(int j=i+i;j<V;j+=i)vis[j]=1; } } const vector<pli> &factor(ll x) { auto [it,ok]=cache.emplace(x,vector<pli>()); if(!ok)return it->second; vector<pli> &res=it->second; ll v=x; if(p>=V&&v%p==0) { int c=0; while(v%p==0) { v/=p; c++; } res.push_back({p,c}); } for(int q:pri) { if((ll)q*q>v)break; if(v%q)continue; int c=0; while(v%q==0) { v/=q; c++; } res.push_back({q,c}); } if(v>1)res.push_back({v,1}); return res; } pair<vector<ll>,Groups> build(const vector<ll> &src) { map<ll,vector<int>> cnt; vector<const vector<pli> *> fac(n); for(int i=0;i<n;i++) { fac[i]=&factor(src[i]); for(auto [q,e]:*fac[i])cnt[q].push_back(e); } map<ll,int> lim; for(auto &[q,v]:cnt) { sort(v.begin(),v.end()); int m=v.size(); if(m>=k)lim[q]=v[m-k]; } Groups g; for(int i=0;i<n;i++) { ll v=1; for(auto [q,e]:*fac[i]) { int c=min(e,lim[q]); while(c--)v*=q; } g[v].val.push_back({src[i],i}); } vector<ll> ans(n); for(auto &[key,t]:g) { int m=t.val.size(); t.pos.resize(m); for(int i=0;i<m;i++)t.pos[i]=t.val[i].second; sort(t.pos.begin(),t.pos.end()); sort(t.val.begin(),t.val.end()); for(int i=0;i<m;i++)ans[t.pos[i]]=t.val[i].first; } return {move(ans),move(g)}; } int get_up(const Group &g,ll v,int id) { int m=g.val.size(); int q=upper_bound(g.val.begin(),g.val.end(),make_pair(v,n))-g.val.begin(); if(q==m) { q--; if(g.pos[q]<id)return id; } else if(g.pos[q]>id) { if(!q)return id; q--; if(g.pos[q]<id)return id; } return g.pos[q]; } int get_down(const Group &g,ll v) { int q=upper_bound(g.val.begin(),g.val.end(),make_pair(v,n))-g.val.begin(); return g.pos[q-1]; } bool cmp(const Change &u,const Change &v,const vector<ll> &base) { int i=0,j=0; while(i<u.n||j<v.n) { int pu=i<u.n?u.a[i].first:n; int pv=j<v.n?v.a[j].first:n; int id=min(pu,pv); ll x=pu==id?u.a[i++].second:base[id]; ll y=pv==id?v.a[j++].second:base[id]; if(x!=y)return x<y; } return 0; } void try_group(ll key,Group &v,Change &best,int &best_id,ll &best_key,Groups &g,const vector<ll> &base,const vector<ll> &tmp) { auto it=g.find(key*p); if(it==g.end())return; Group &u=it->second; for(int id:v.pos) { ll val=a[id]*p; int up=get_up(u,val,id); int down=get_down(v,a[id]); if(down<min(up,id)||up==id)continue; Change cur={}; if(up>id) { auto q=upper_bound(u.pos.begin(),u.pos.end(),id); int next=*q; if(base[next]>a[id])continue; cur.a[cur.n++]={id,base[next]}; } else { cur.a[cur.n++]={up,val}; auto q=lower_bound(u.pos.begin(),u.pos.end(),id); q--; cur.a[cur.n++]={id,base[*q]}; } if(down<id) { auto q=upper_bound(v.pos.begin(),v.pos.end(),down); cur.a[cur.n++]={down,base[*q]}; } for(int i=0;i<cur.n;i++) { for(int j=i+1;j<cur.n;j++)if(cur.a[j]<cur.a[i])swap(cur.a[i],cur.a[j]); } if(cmp(cur,best,tmp)) { best=cur; best_id=id; best_key=key; } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); init(); int t; cin>>t; while(t--) { cin>>n>>k>>p; cache.clear(); a.resize(n); for(int i=0;i<n;i++)cin>>a[i]; auto res=build(a); vector<ll> base=move(res.first); Groups g=move(res.second); vector<ll> ans=base; if(p!=1) { Change best={}; int id=-1; ll key=0; for(auto &[v,u]:g)try_group(v,u,best,id,key,g,base,base); if(id!=-1) { Group &v=g[key]; int m=v.pos.size(); int down=get_down(v,a[id]); vector<ll> tmp=base; for(int i=0;i+1<m;i++)if(v.pos[i]>=down)tmp[v.pos[i]]=tmp[v.pos[i+1]]; best={}; id=-1; try_group(key,v,best,id,key,g,base,tmp); a[id]*=p; ans=build(a).first; } } for(int i=0;i<n;i++) { if(i)cout<<' '; cout<<ans[i]; } cout<<'\n'; } return 0; } ```