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");
}

 

posted @ 2026-04-06 12:35  董晓  阅读(83)  评论(0)    收藏  举报