D126 同余最短路 Dijkstra 算法 CF986F Oppa Funcan Style Remastered
D126 同余最短路 Dijkstra 算法 CF986F Oppa Funcan Style Remastered_哔哩哔哩_bilibili
CF986F Oppa Funcan Style Remastered - 洛谷 置换和排列 - OI Wiki 分解质因数 - OI Wiki
给定 n 与 k,问是否能把 n 写成若干个 k 的非 1 因子之和(因子可以重复使用)。$n\le 10^{18},k\le 10^{15}$。
$n=5,k=6,YES$,因为 $5$ 个数可以拆成两组,两组的轮换周期为 $3\times 2=6$,$6$ 秒后能恢复初序
$(123)(45)→(231)(54)→(312)(45)→(123)(54)→(231)(45)→(312)(54)→(123)(45)$
$n=3,k=8,NO$,因为 $3$ 个数的轮换周期不是 $8$ 的因子,$8$ 秒后不能恢复初序
思路
把 $n$ "拆成若干因子之和" 与 "拆成若干质因子之和" 是等价的,如 $7=4+3=2+2+3$
先用欧拉筛法预处理出 $3.5 \times 10^7$ 以内的质数,因为 $k\le 10^{15}$,所以 $k$ 至多包含一个大于 $3.5 \times 10^7$ 的质因子
这个大质因子可以在下面的分解质因数时求出,我们把 $k$ 分解的所有质因子用 p 数组存下来
1. 如果质因子只有一个,如果 $n$ 为该质因子的倍数,那么有解
2. 如果质因子只有两个,就是判断不定方程 $ax+by=n$ 是否有解($a,b$ 是质因子),对方程两边模 a 处理
$ax+by\equiv n\;(mod\;a)$
$by\equiv n\;(mod\;a)$
$y\equiv \frac{n}{b}\;(mod\;a)$
用快速幂求出 $b$ 的乘法逆元 $c$,得出最小解 $y=n*c$,那么同余方程有解的条件就是 $y\le \frac {n}{b}$,即 $y*b\le n$
3. 如果质因子多于两个,就以 $p_1$ 做模,跑同余最短路,如果 $d[n\%p_1]\le n$,那么 $n$ 能用质因子拼出
为什么两个质因子不能跑最短路?
例如 $k=958938326785573=30379903 \times 31564891$,质数 $p_1$ 做模太大,会爆空间
如果 $k(\le 10^{15})$ 包含三个质因子,那么最小的质因子 $p_1$ 最大也不会超过 $10^5$,所以不会爆空间
注意:题目测试数量 $t\le 10^4$,并且保证同一组测试中,不同的 $k$ 的数量不超过 50
提醒我们要离线保存 $t$ 个数据,以 $k$ 为键用 $map$ 保存 $(k,n,i)$,然后离线处理的次数就降为 50
时间复杂度降为 $O(50\times \frac {\sqrt k}{lnk}+50\times Nlog(N))$
相关板子:
G08【模板】筛质数 埃氏筛法 欧拉筛法 - 董晓 - 博客园
G13【模板】同余式 乘法逆元 费马小定理 - 董晓 - 博客园D124 同余最短路 Dijkstra 算法 P8060 [POI 2003] Sums - 董晓 - 博客园
// 同余最短路 Dijkstra 算法 #include<bits/stdc++.h> #define ll long long #define pii pair<ll,int> using namespace std; const int N=100010,M=3.5e7; bool comp[M]; vector<int>prim; ll n,k,p[N],cnt,d[N]; map<ll,vector<pair<ll,int>> >mp; int ans[N]; void Euler(){ //欧拉筛法 for(int i=2;i<=M;i++){ if(!comp[i]) prim.push_back(i); for(int j=0;j<prim.size()&&i*prim[j]<=M;j++){ comp[i*prim[j]]=true; if(i%prim[j]==0) break; } } } ll qsm(ll a,ll b,ll p){ //快速幂 ll res=1; while(b){ if(b&1) res=(res*a)%p; b>>=1; a=(a*a)%p; } return res; } void dijkstra(){ for(int i=0;i<p[1];i++)d[i]=2e18; d[0]=0; priority_queue<pii,vector<pii>,greater<pii> > q; q.push({0,0}); while(!q.empty()){ auto [dd,u]=q.top(); q.pop(); if(dd!=d[u])continue; for(int i=2;i<=cnt;i++){ //枚举其它质因子 int v=(u+p[i])%p[1]; if(d[v]>d[u]+p[i]){ d[v]=d[u]+p[i]; //最短路 q.push({d[v],v}); } } } } int main(){ Euler(); int t; scanf("%d",&t); for(int i=1;i<=t;i++){ scanf("%lld%lld",&n,&k); mp[k].push_back({n,i}); //离线保存,k<=50,每个k有多个不同的n } for(auto [fir,sec]:mp){ //离线处理,最多50次 cnt=0; k=fir; if(k==1) continue; for(int i=0;i<prim.size()&&1ll*prim[i]*prim[i]<=k;i++){ //分解质因数 if(k%prim[i]==0){ //如果pi是k的质因子 p[++cnt]=prim[i]; while(k%prim[i]==0) k/=prim[i]; //除掉质因子pi } } if(k>1) p[++cnt]=k; //记录最大的质因子 if(cnt==1){ //一个质因子 for(auto [n,i]:sec){ if(n%p[cnt]==0) ans[i]=1; //如果n是质因子的倍数 else ans[i]=0; } } else if(cnt==2){ //两个质因子(均>1e7时不能用最短路) for(auto [n,i]:sec){ if(n%p[1]==0||n%p[2]==0){ //如果n是质因子的倍数 ans[i]=1; continue; } ll a=p[1],b=p[2]; if((n%a*qsm(b,a-2,a)%a)*b<=n) ans[i]=1; //例如 n=29,a=5,b=7 else ans[i]=0; } } else{ //多个质因子 dijkstra(); //同余最短路 for(auto [n,i]:sec){ if(d[n%p[1]]<=n) ans[i]=1; //如果n>=能被质因子拼出的最小数,那么n能被拼出 else ans[i]=0; } } } for(int i=1;i<=t;i++) printf("%s\n",ans[i]?"YES":"NO"); }
浙公网安备 33010602011771号