失控的未来交通工具 (LOJ 508,带权并查集,数论)
LOJ 508 失控的未来交通工具 (带权并查集 + 数论)
$ solution: $
很综合的一道难题。看了让人不知所措,数据范围又大,题目描述又不清晰。只能说明这道题有很多性质,或者很多优化。
好了,回来讲正解。像这种在图上寻找路径,并且对路径取模,尤其还不是简单路径的题,基本上都和环的性质有关。因为环我们可以无限跑啊!而这一道题在环上路径长度取模,需要联系到一个结论(就是天天爱跑步):我可以加一个数 $ w $ 无限次,得到的数要对 $ m $ 取模,那么我最终能且仅能得到所有小于 $ m $ 的 $ gcd(w,m) $ 的倍数。知道了这个结论,本题就只需要再讨论三个问题了:
1. 当模数为奇数时:(这个不算正解一部分,只能有开导作用)
当 $ m $ 是个奇数时,我们可以在某条环上绕圈(注意单独的一条边也算环,因为我们可以来回跑)。对于某条长度为 $ w $ 的边 $ (x,y) $ ,如果我们想只让这一条边/环计入贡献,我们可以这样操作:(设询问里起点为 $ u $ 终点为 $ v $ )在 $ u, x $ 之间来回跑 $ m $ 次,然后 $ (x,y) $ 只跑一次,然后再在 $ y,v $ 之间跑 $ m $ 次。这样我们两边的长度因为取模被削去,只留下那中间一条边的贡献!
于是我们通过这条边能走的长度一定是 $ gcd (m, w) $ 的倍数。设 $ u,v $ 所在连通块有 $ k $ 条边,那么走的路径一定是 $ G = gcd (m, w_1 , w_2 , w_3 , ... ,w_k) $ 的倍数。于是我们开一个并查集维护连通块的 $ gcd $ 就好。当然我们首先还需要得到随便一条可以从 $ u $ 出发走到 $ v $ 的路径得长度,再从这个长度上添加或消除边的贡献,这个我们也可以用带权并查集维护。
2. 如何处理回答的信息
$ upd: $ 经 $ LYH $ 大佬的提醒,现将本段一些错误更新。
题目意思大致:我们有一条路径 $ (u,v) $ ,询问在 $ x, x + b, ... , x + (c − 1)\times b $ 这些数中,有多少数在原图上存在一条从 $ u $ 到 $ v $ 的路径,使得这个数和路径长度模 $ m $ 同余。
首先上面我们说了,我们用并查集维护一个值(暂时叫 $ G $ ),所有为 $ G $ 的倍数长度的路径我们都可以走到,于是题目所求就变成了:在 $ x, x + b, ... , x + (c − 1)\times b $ 这些数中,有多少数存在一条路径,使得这个数和这条路径长度的差是 $ G $ 的倍数。 (注意是差值)
可以知道当 $ c $ 很大的时候,我们肯定不能暴力判断。我们分析性质发现给出的数列是一个等差数列。所以这个数列在模 $ G $ 意义下一定是循环的,所以只需要找到第一个模 $ G $ 等于0的位置(就是说这个数是 $ G $ 的倍数)与循环节长度即可。即要求解同余方程 $ x + k\times b ≡ d[u]+d[v] ~~(mod~G) \quad -> \quad k\times b~+ y\times G = d[u]+d[v]-x $ 中 $ k $ 的值,这个用扩展欧几里得就好。而为了更契合扩展欧几里得,博主在代码里将这个同余方程的系数用 $ A,B,C $ 表示。
rg A=b,B=g[fx],G=gcd(A,B),C=(ll)d[u]+d[v]-w+B; //列出同余方程的系数
// 同余方程: C - x*A ≡ 0 (mod B) -> x*A + y*B = C (对应上文第二个式子!)
if(C%G){puts("0"); continue;} //方程无解,请参见扩欧的做法
rg x,y; A/=G; B/=G; C/=G; //请参见扩欧的解方程的步骤
exgcd(A,B,x,y); x=((ll)x*C%B+B)%B; //解出同余方程
if(x<c)ans=(c-1-x)/B+1; //注意我们找的是x的最小解,循环长度是模数B! (同余方程性质)
printf("%d\n",ans); //输出答案
3. 当模数为偶数:(这里才是正解)
这种情况可以用与奇数相似的做法,但是比奇数复杂得多!当 $ m $ 是个偶数时,我们可以在某条环上绕圈,但是我们绕 $ m $ 次之后会回到原点,这代表着我们只能得到 $ gcd(m,2\times w) $ 的倍数的路径长度(好好理解,这个需要自己思考),这个贡献和跑一遍含有这条边的环的贡献 $ gcd(m,k+m) $ 可能不一样!
所以我们考虑另一种做法:首先,我们依旧要维护边的贡献 $ gcd(m,2\times w) $ ,然后考虑将环的贡献也加进去。这个我们只要在并查集合并时维护,只有当两个节点同属于一个联通块时我们加入这条边才会产生环。然后我们只要维护这两个点到并查集的根的路径加上这条边产生的环的贡献即可。(以下图为例)我们此时并查集里根为1号结点,我们现在加入 $ w $ 边,我们只要维护 $ (a,b,w) $ 形成的环即可。虽然 $ w $ 边还可以和 $ (c,d) $ 形成环,但是我们之后也可以这样操作使的两个环相互转换:从2号结点跑环 $ w,a,b $ 回到2节点,再跑 $ (c,d,a,b) $ 回到2节点,这时 $ (a,b) $ 两条边跑了两次我们只要再 $ (a,b) $ 来回跑 $ m-2 $ 次即可消去 $ (a,b) $ 两边的的贡献,将环变成 $ (w,d,c) $ 。
同理我们还可以实现 $ (w,a,b) $ 到 $ (a,b,c,d) $ 环的转化,这里不做多讲,不会可以直接留言。这种联通块里加入边的贡献我们可以这样记录: $ G=gcd(G,d[u]+d[v]+w,2\times w) $ ,其中第一个是联通块原贡献值,第二个是包含 $ w $ 边的环的贡献,第三个是 $ w $ 这条边单独的贡献(在最开始讲了)。
于是我们用带权并查集维护节点间随意某一条路径,再从这个长度上用上述方法添加或消除环和边的贡献,这样可以得到一个合并后的总贡献 $ G $ 的所有倍数的路径长度。然后我们直接再按照第二个问题的做法做就可以了!
$ code: $
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
int n,m,q;
int d[1000005]; //并查集里面节点到其联通块的根的距离
int g[1000005]; //当前联通块的gcd,如天天爱跑步
int fa[1000005]; //并查集
inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
inline int gcd(int a,int b){
if(b==0)return a; return gcd(b,a%b);
}
inline int getfa(int x){
if(x==fa[x])return x;
rg to=getfa(fa[x]); //注意顺序,防止信息被覆盖
d[x]=(d[x]+d[fa[x]])%m; //更新当前节点到根的距离
return fa[x]=to; //最后再赋值
}
inline void exgcd(int a,int b,int &x,int &y){ //扩展欧几里得
if(b==0) x=1,y=0; //直接构造一组解
else exgcd(b,a%b,y,x),y-=a/b*x; //我们不需要求gcd,只要解方程就好
}
int main(){
n=qr(); m=qr(); q=qr();
for(rg i=1;i<=n;++i) fa[i]=i,g[i]=m;
for(rg i=1;i<=q;++i){
rg op=qr(),u=qr(),v=qr(),w=qr();
rg fx=getfa(u),fy=getfa(v); //查找根
if(op==1){
if(fx!=fy){ fa[fx]=fy; //合并
d[fx]=((ll)d[u]+d[v]+w)%m; //一个联通块只有一个根,所以另一个需要改距离
g[fy]=gcd(gcd(g[fx],g[fy]),2*w); //更新联通块的gcd
}else g[fx]=gcd(gcd(g[fx],((ll)d[u]+d[v]+w)%m),2*w); //新加了一个环,合并环的贡献
} else{ //请注意大小写!!!!!!
rg ans=0,b=qr()%g[fx],c=qr(); //读入
if(fx!=fy){puts("0"); continue;} //不连通意味着不能到达
rg A=b,B=g[fx],G=gcd(A,B),C=(ll)d[u]+d[v]-w+B; //列出同余方程的系数
// 同余方程: C + x*A = 0 (mod B) -> x*A + y*B = C (mod g[fx])
if(C%G){puts("0"); continue;} //方程无解,请参见扩欧的做法
rg x,y; A/=G; B/=G; C/=G; //请参见扩欧的解方程的步骤
exgcd(A,B,x,y); x=((ll)x*C%B+B)%B; //解出同余方程
if(x<c)ans=(c-1-x)/B+1; //注意我们找的是x的最小解,循环长度是模数B! (同余方程性质)
printf("%d\n",ans); //输出答案
}
}
return 0;
}
失控的未来交通工具 (LOJ 508,带权并查集,数论)的更多相关文章
- POJ 1703 Find them, Catch them(带权并查集)
传送门 Find them, Catch them Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 42463 Accep ...
- [NOIP摸你赛]Hzwer的陨石(带权并查集)
题目描述: 经过不懈的努力,Hzwer召唤了很多陨石.已知Hzwer的地图上共有n个区域,且一开始的时候第i个陨石掉在了第i个区域.有电力喷射背包的ndsf很自豪,他认为搬陨石很容易,所以他将一些区域 ...
- poj1417 带权并查集 + 背包 + 记录路径
True Liars Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2713 Accepted: 868 Descrip ...
- poj1984 带权并查集(向量处理)
Navigation Nightmare Time Limit: 2000MS Memory Limit: 30000K Total Submissions: 5939 Accepted: 2 ...
- 【BZOJ-4690】Never Wait For Weights 带权并查集
4690: Never Wait for Weights Time Limit: 15 Sec Memory Limit: 256 MBSubmit: 88 Solved: 41[Submit][ ...
- hdu3038(带权并查集)
题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示 ...
- 洛谷OJ P1196 银河英雄传说(带权并查集)
题目描述 公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦 创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争.泰山 ...
- poj1984 带权并查集
题意:有多个点,在平面上位于坐标点上,给出一些关系,表示某个点在某个点的正东/西/南/北方向多少距离,然后给出一系列询问,表示在第几个关系给出后询问某两点的曼哈顿距离,或者未知则输出-1. 只要在元素 ...
- poj1611 带权并查集
题意:病毒蔓延,现在有 n 个人,其中 0 号被认为可能感染,然后给出多个社交圈,如果某个社交圈里有人被认为可能被感染,那么所有这个社交圈里的人都被认为可能被感染,现在问有多少人可能被感染. 带权并查 ...
随机推荐
- Visual Studio Code - 代码提示使用 webpack alias 的模块
使用 PathIntellisense 还是使用jsconfig.json? 使用 PathIntellisense 只能提示模块路径,并无法让 vs code 的 Intellisense 知道这个 ...
- 【Linux 应用编程】进程管理 - 进程间通信IPC之管道 pipe 和 FIFO
IPC(InterProcess Communication,进程间通信)是进程中的重要概念.Linux 进程之间常用的通信方式有: 文件:简单,低效,需要代码控制同步 管道:使用简单,默认阻塞 匿名 ...
- Intellij Idea使用教程汇总篇
Java编程强大的工具IDEA使用教程及一些快捷键收藏如下: https://blog.csdn.net/fanrenxiang/article/details/80503490
- 第九周课程总结&实验报告7
实验任务详情: 完成火车站售票程序的模拟.要求:(1)总票数1000张:(2)10个窗口同时开始卖票:(3)卖票过程延时1秒钟:(4)不能出现一票多卖或卖出负数号票的情况. 实验代码: package ...
- exists、in和join比较
这个根据实际情况具体分析 遇到问题了再具体分析就行.
- CentOS 7.6 RPM 方式安装Oracle19c 后 使用 systemd 的方式设置开机自动启动Oracle数据库
1. 方法简介: 使用systemd 来进行 oracle数据库的启动和关闭操作. 使用的脚本为 lsnrctl和dbstart 2. 修改事项. 需要先修改一下 oracle 的启动脚本配置: vi ...
- 1000行基本SQL
/* Windows服务 */ -- 启动MySQL net start mysql -- 创建Windows服务 sc create mysql binPath= mysqld_bin_path(注 ...
- UUID工具类及使用
1.工具类: package UUIdtest; import java.util.UUID; public class UUIDUtil { public static String getUUID ...
- shell script简单笔记
变量 shell script是一种脚本语言,变量的定义是通过 myName=HYB 这样的形式定义的. 当存在空格时,可以通过双引号或单引号将其变为字符串.双引号不进行转义,单引号将内容进行转义为一 ...
- HDU-1018 BigNumber
Big Number Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total ...