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,带权并查集,数论)的更多相关文章

  1. POJ 1703 Find them, Catch them(带权并查集)

    传送门 Find them, Catch them Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 42463   Accep ...

  2. [NOIP摸你赛]Hzwer的陨石(带权并查集)

    题目描述: 经过不懈的努力,Hzwer召唤了很多陨石.已知Hzwer的地图上共有n个区域,且一开始的时候第i个陨石掉在了第i个区域.有电力喷射背包的ndsf很自豪,他认为搬陨石很容易,所以他将一些区域 ...

  3. poj1417 带权并查集 + 背包 + 记录路径

    True Liars Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 2713   Accepted: 868 Descrip ...

  4. poj1984 带权并查集(向量处理)

    Navigation Nightmare Time Limit: 2000MS   Memory Limit: 30000K Total Submissions: 5939   Accepted: 2 ...

  5. 【BZOJ-4690】Never Wait For Weights 带权并查集

    4690: Never Wait for Weights Time Limit: 15 Sec  Memory Limit: 256 MBSubmit: 88  Solved: 41[Submit][ ...

  6. hdu3038(带权并查集)

    题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示 ...

  7. 洛谷OJ P1196 银河英雄传说(带权并查集)

    题目描述 公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦 创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争.泰山 ...

  8. poj1984 带权并查集

    题意:有多个点,在平面上位于坐标点上,给出一些关系,表示某个点在某个点的正东/西/南/北方向多少距离,然后给出一系列询问,表示在第几个关系给出后询问某两点的曼哈顿距离,或者未知则输出-1. 只要在元素 ...

  9. poj1611 带权并查集

    题意:病毒蔓延,现在有 n 个人,其中 0 号被认为可能感染,然后给出多个社交圈,如果某个社交圈里有人被认为可能被感染,那么所有这个社交圈里的人都被认为可能被感染,现在问有多少人可能被感染. 带权并查 ...

随机推荐

  1. Flask 中的蓝图

    相当与django中的App 基础模板 1.初识Flask蓝图(blueprint) 创建一个项目然后将目录结构做成: s_view.py 文件中的内容   from flask import Blu ...

  2. spring +spring+ hibernate配置1

    这种配置方式是将Spring .SpringMVC.Hibernate三个模块分开配置,交叉引用!hibernate连接配置使用.properties文件 web.xml配置 <web-app ...

  3. k8s架构

    master节点 k8s的集群由master和node组成,节点上运行着若干k8s服务. master节点之上运行着的后台服务有kube-apiserver .kube-scheduler.kube- ...

  4. FiddlerCore修改http返回结果

    static void FiddlerApplication_BeforeRequest(Session oSession) { oSession.bBufferResponse = true; } ...

  5. 阿里云 Centos7 部署 Django 项目

    前期准备 阿里云服务器 mysql数据库 已经本地运行成功的项目 阿里云服务器的环境配置 Git #代码管理 Gitlab #代码托管,要求服务器内存不低于2G,我选择放弃 Mysql #连接数据库 ...

  6. 【ABAP系列】SAP ABAP SY-SUBRC的含义解析

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP SY-SUBR ...

  7. [转帖]OEM、ODM、OBM分别是什么?

    OEM.ODM.OBM分别是什么? https://blog.csdn.net/liangtianmeng/article/details/83215500 感觉很多地方说明的都不对 OEM 别人的产 ...

  8. CentOS 7 下安装.NET Core SDK 2.1

    一.RPM包安装 1.导入rpm源 sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod ...

  9. Kinect V2入门之数据获取步骤

    在Kinect for windows SDK2.0中,获取并处理数据源接口步骤如下: Sensor -> Source -> Reader -> Frame -> Data ...

  10. [BZOJ 4332] [JSOI2012]分零食(DP+FFT)

    [BZOJ 4332] [JSOI2012]分零食(DP+FFT) 题面 同学们依次排成了一列,其中有A位小朋友,有三个共同的欢乐系数O,S和U.如果有一位小朋友得到了x个糖果,那么她的欢乐程度就是\ ...