这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
2020-2021:teams:legal_string:jxm2001:同余最短路 [2021/09/03 21:11] jxm2001 |
2020-2021:teams:legal_string:jxm2001:同余最短路 [2021/09/05 08:46] (当前版本) jxm2001 [例题三] |
||
---|---|---|---|
行 3: | 行 3: | ||
===== 算法简介 ===== | ===== 算法简介 ===== | ||
- | 用于计算 $k_1a_1+k_2a_2+\cdots +k_na_n$ 在 $[0,m]$ 范围内不能表示的数的算法。 | + | 用于计算 $k_1a_1+k_2a_2+\cdots +k_na_n$ 在 $[0,m]$ 范围内能表示的数的算法。 |
===== 算法实现 ===== | ===== 算法实现 ===== | ||
行 11: | 行 11: | ||
于是可以 $O(n\times a\log a)$ 计算出最小的可以表示成 $ka_1+r(0\le r\lt a_1)$ 的数,于是每个 $r$ 对答案的贡献为 $\lfloor \frac {m-\text{dis}(r)}{a_1}\rfloor$。 | 于是可以 $O(n\times a\log a)$ 计算出最小的可以表示成 $ka_1+r(0\le r\lt a_1)$ 的数,于是每个 $r$ 对答案的贡献为 $\lfloor \frac {m-\text{dis}(r)}{a_1}\rfloor$。 | ||
- | 不难发现 $a_1$ 越小越好,另外每个点的相邻点可以在跑最短路算法时动态计算,这些都有利于卡常和节省空间。 | + | 不难发现可以重新排序选最小的 $a$ 作为 $a_1$,另外每个点的相邻点可以在跑最短路算法时动态计算,这些都有利于卡常和节省空间。 |
===== 算法例题 ===== | ===== 算法例题 ===== | ||
+ | |||
+ | ==== 例题一 ==== | ||
[[https://www.luogu.com.cn/problem/P2371|洛谷p2371]] | [[https://www.luogu.com.cn/problem/P2371|洛谷p2371]] | ||
+ | |||
+ | 板子题。 | ||
<hidden 查看代码> | <hidden 查看代码> | ||
行 59: | 行 63: | ||
dj(n); | dj(n); | ||
enter(calc(qr)-calc(ql-1)); | enter(calc(qr)-calc(ql-1)); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ==== 例题二 ==== | ||
+ | |||
+ | [[http://acm.hdu.edu.cn/showproblem.php?pid=6071|HDU6071]] | ||
+ | |||
+ | === 题意 === | ||
+ | |||
+ | 给定一个四元环,环上每条边有一个边权,且重复经过则计算多次贡献。 | ||
+ | |||
+ | 人物从二号点出发,最终回到二号点,问权值不小于 $k$ 的路径的最小权值。 | ||
+ | |||
+ | === 题解 === | ||
+ | |||
+ | 任取一条与二号点相邻的边,设权值为 $w$。设 $\text{dis}(i,j)$ 表示从二号点出发到达 $i$ 号点且距离模 $2w$ 等于 $j$ 的最小距离。 | ||
+ | |||
+ | 那么从二号点出发能得到的路径的权值集合为 $\{\text{dis}(2,r)+2kw|0\le r\lt 2w,k\ge 0\}$,枚举 $r$ 即可计算答案。 | ||
+ | |||
+ | 关于为什么选取 $2w$,可以解释为回到 $2$ 号点后可以在任意一条边上来回移动,正确性不难证明。 | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=5,MAXV=6e4+5; | ||
+ | const LL inf=2e18; | ||
+ | int d[MAXN]; | ||
+ | vector<pair<int,int> > g[MAXN]; | ||
+ | LL dis[MAXN][MAXV]; | ||
+ | bool vis[MAXN][MAXV]; | ||
+ | void dj(int n){ | ||
+ | priority_queue<pair<LL,int> > q; | ||
+ | q.push(make_pair(0,1)); | ||
+ | _for(i,0,4)_for(j,0,n) | ||
+ | dis[i][j]=inf,vis[i][j]=false; | ||
+ | dis[1][0]=0; | ||
+ | while(!q.empty()){ | ||
+ | int u1=q.top().second; | ||
+ | int u2=(-q.top().first)%n; | ||
+ | q.pop(); | ||
+ | if(vis[u1][u2]) | ||
+ | continue; | ||
+ | vis[u1][u2]=true; | ||
+ | for(pair<int,int> p:g[u1]){ | ||
+ | int v1=p.first,w=p.second,v2=(u2+w)%n; | ||
+ | if(dis[v1][v2]>dis[u1][u2]+w){ | ||
+ | dis[v1][v2]=dis[u1][u2]+w; | ||
+ | q.push(make_pair(-dis[v1][v2],v1)); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | LL calc(int val,LL k){ | ||
+ | LL ans=inf; | ||
+ | _for(i,0,val){ | ||
+ | if(dis[1][i]>k) | ||
+ | ans=min(ans,dis[1][i]); | ||
+ | else | ||
+ | ans=min(ans,dis[1][i]+(k-dis[1][i]+val-1)/val*val); | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | void solve(){ | ||
+ | LL k=read_LL(); | ||
+ | _for(i,0,4) | ||
+ | g[i].clear(); | ||
+ | _for(i,0,4){ | ||
+ | d[i]=read_int(); | ||
+ | g[i].push_back(make_pair((i+1)%4,d[i])); | ||
+ | g[(i+1)%4].push_back(make_pair(i,d[i])); | ||
+ | } | ||
+ | dj(2*d[0]); | ||
+ | enter(calc(2*d[0],k)); | ||
+ | } | ||
+ | int main(){ | ||
+ | int T=read_int(); | ||
+ | while(T--) | ||
+ | solve(); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ==== 例题三 ==== | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/CF986F|CF986F]] | ||
+ | |||
+ | === 题意 === | ||
+ | |||
+ | $T$ 组询问,每组询问给定 $n,k$,求 $n$ 是否能用若干个 $k$ 的不为 $1$ 的因子表示。 | ||
+ | |||
+ | 数据保证 $k$ 的取值不超过 $50$ 个。 | ||
+ | |||
+ | === 题解 === | ||
+ | |||
+ | 如果 $n$ 能用若干个 $k$ 的不为 $1$ 的因子表示等价于 $n$ 能用 $k$ 的素因子表示。 | ||
+ | |||
+ | 预处理 $O(\sqrt k)$ 范围的数后对每个 $k$ 进行因式分解,时间复杂度 $O\left(\frac {\sqrt k}{\ln k}\right)$。 | ||
+ | |||
+ | 如果 $k=1$,显然无解。如果 $k$ 是质数,只需要判定 $k\mid n$ 是否成立。 | ||
+ | |||
+ | 如果 $k$ 有两种素因子,记为 $a,b$,于是等价于求解 $ax+by=n$ 的非负整数解。 | ||
+ | |||
+ | 找到最小的 $y$ 满足 $y\equiv nb^{-1}\bmod x$,然后验证此时 $by$ 是否不超过 $n$ 即可。 | ||
+ | |||
+ | 如果 $k$ 有至少三种素因子,显然 $k$ 的最小素因子不超过 $O\left(k^{\frac 13}\right)$,跑同余最短路算法即可,时间复杂度 $O\left(k^{\frac 13}\log k\right)$。 | ||
+ | |||
+ | 总时间复杂度 $\left(50\left(\frac{\sqrt k}{\ln k}+k^{\frac 13}\log k\right)\right)$。 | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXV=4e7,MAXN=1e5+5,MAXQ=1e4+5; | ||
+ | const LL inf=1e18+5; | ||
+ | int prime[MAXV],pcnt; | ||
+ | bool vis[MAXV]; | ||
+ | void Init(){ | ||
+ | _for(i,2,MAXV){ | ||
+ | if(!vis[i])prime[pcnt++]=i; | ||
+ | for(int j=0;j<pcnt&&i*prime[j]<MAXV;j++){ | ||
+ | vis[i*prime[j]]=true; | ||
+ | if(i%prime[j]==0) | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | vector<LL> g; | ||
+ | void get_frac(LL n){ | ||
+ | g.clear(); | ||
+ | _for(i,0,pcnt){ | ||
+ | if(n%prime[i]==0){ | ||
+ | g.push_back(prime[i]); | ||
+ | while(n%prime[i]==0)n/=prime[i]; | ||
+ | } | ||
+ | } | ||
+ | if(n!=1) | ||
+ | g.push_back(n); | ||
+ | } | ||
+ | int quick_pow(int n,int k,int mod){ | ||
+ | int ans=1; | ||
+ | while(k){ | ||
+ | if(k&1)ans=1LL*ans*n%mod; | ||
+ | n=1LL*n*n%mod; | ||
+ | k>>=1; | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | LL dis[MAXN]; | ||
+ | void dj(int n){ | ||
+ | _for(i,0,n){ | ||
+ | dis[i]=inf; | ||
+ | vis[i]=false; | ||
+ | } | ||
+ | dis[0]=0; | ||
+ | priority_queue<LL> q; | ||
+ | q.push(0); | ||
+ | while(!q.empty()){ | ||
+ | int u=(-q.top())%n;q.pop(); | ||
+ | if(vis[u]) | ||
+ | continue; | ||
+ | vis[u]=true; | ||
+ | _for(i,1,g.size()){ | ||
+ | int v=(u+g[i])%n; | ||
+ | if(dis[v]>dis[u]+g[i]){ | ||
+ | dis[v]=dis[u]+g[i]; | ||
+ | q.push(-dis[v]); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | bool query(LL n){ | ||
+ | if(g.size()==0) | ||
+ | return false; | ||
+ | if(g.size()==1) | ||
+ | return n%g[0]==0; | ||
+ | else if(g.size()==2){ | ||
+ | LL x=g[0],y=g[1]; | ||
+ | int b=(n%x)*quick_pow(y%x,x-2,x)%x; | ||
+ | return y*b<=n; | ||
+ | } | ||
+ | else{ | ||
+ | int m=g[0]; | ||
+ | return dis[n%m]<=n; | ||
+ | } | ||
+ | } | ||
+ | map<LL,vector<pair<int,LL> > >mp; | ||
+ | bool ans[MAXQ]; | ||
+ | void solve(LL k,vector<pair<int,LL> > a){ | ||
+ | get_frac(k); | ||
+ | if(g.size()>2) | ||
+ | dj(g[0]); | ||
+ | for(pair<int,LL> p:a) | ||
+ | ans[p.first]=query(p.second); | ||
+ | } | ||
+ | int main(){ | ||
+ | Init(); | ||
+ | int q=read_int(); | ||
+ | _for(i,0,q){ | ||
+ | LL n=read_LL(),k=read_LL(); | ||
+ | mp[k].push_back(make_pair(i,n)); | ||
+ | } | ||
+ | for(map<LL,vector<pair<int,LL> > >::iterator iter=mp.begin();iter!=mp.end();iter++) | ||
+ | solve(iter->first,iter->second); | ||
+ | _for(i,0,q){ | ||
+ | if(ans[i]) | ||
+ | puts("YES"); | ||
+ | else | ||
+ | puts("NO"); | ||
+ | } | ||
return 0; | return 0; | ||
} | } | ||
</code> | </code> | ||
</hidden> | </hidden> |