点此看题面

大致题意: 给你一张无向图,每条边的边权为\(2^{x_i}\),求\(s\)到\(t\)的最短路。

最短路

最短路,首先考虑\(Dijkstra\)。这里用\(SPFA\)似乎不太好,因为此题中计算边权是比较费时间的。

说句实话,这里的最短路和普通的最短路是一样的,唯一区别就是边权很大。

则我们需要支持的操作就应该是大二进制数的加法和比大小。

线段树?——加法

先考虑最暴力的,我们对于每个点,开一个线段树,每一位维护二进制下这一位的值,表示其距离。

然后由于边权是\(2\)的幂,所以也就相当于在二进制下某一位上加上\(1\)。

这看似简单,但要进位啊!而且加法结束后马上就是比大小,因此这是刻不容缓的。

不过没关系,我们可以研究进位的性质,例如下面这个例子:

 1011101
+ 1000
--------
1100101

这么一看,性质好像还是挺显然的,就是找出高于或等于当前加\(1\)位(设其为\(x\))的最低的为\(0\)的位(设其为\(t\)),然后把\(x\sim t-1\)这些位上改为\(0\),把第\(t\)位改为\(1\)即可。

简单地总结一下,就是要实现区间赋值为\(0\)和单点赋值为\(1\)两种操作,这似乎是线段树的基本操作?

但新的问题来了:如何求出高于或等于当前加\(1\)位的最低为\(0\)的位?

二分!

考虑当前二分到的位为\(mid\),那么若\(x\sim mid\)间的\(1\)的个数小于等于\(t-x\),就说明\(x\sim mid\)之间存在至少一个\(0\),\(Check()\)返回\(true\),否则返回\(false\)。

而要求\(1\)的个数,也是线段树的基本操作吧,记一下每个点子树内的\(Size\)然后区间查询即可。

线段树?——比大小

现在考虑如何比较两个大二进制数的大小。

我们可以从两棵线段树的根节点出发,由于比较的是最高位,所以若某一节点右儿子内有\(1\)(\(Size>0\)),另一节点没有,就可以直接比较出大小。

若两个节点右儿子内都没有\(1\),就去比较左儿子。

但如果两个节点右儿子内都有\(1\),就比较棘手了,因为如果我们直接去比较右儿子,如果比完之后发现右儿子完全一样,我们又得去比较左儿子,复杂度就退化成了\(O(n)\)。

仔细思考,便可以发现这个做法错误的关键就在于两个右儿子可能完全一样无法比较大小。

那么如果我们能快速判断两个右儿子是否一样,不就可以直接去比较左儿子,而避免这个问题了吗?

于是就可以想到哈希,这样就轻松避免了复杂度的退化。

主席树

通过上面的总结,我们可以发现,用线段树可以轻松维护这些信息。

但是,之前说的对于每个点开一棵线段树显然不现实,因此就要主席树

这样一来,这道题就彻底做完了。

莫名其妙

呃,这里提一下,我在具体实现时碰到一个诡异的问题。

不知道为什么,我写\(Dijkstra\)时调用\(STL\)的优先队列莫名挂了,调到心态爆炸后手写了一个线段树,结果就过了?!

莫名其妙。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 110000
#define LN 20
#define X 1000000007
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
using namespace std;
int n,m,s,t,ee,lnk[N+5];struct edge {int to,nxt,val;}e[(N<<1)+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
class ChairmanTree//主席树
{
private:
#define L l,mid,O[rt].S[0]
#define R mid+1,r,O[rt].S[1]
#define PU(x)\
(\
O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz,\
O[x].H=O[O[x].S[0]].H+O[O[x].S[1]].H\
)//上传信息,维护子树内1的个数和哈希值
int tot,p[N+5];
class Hash//哈希
{
private:
#define ull unsigned long long
#define RU Reg ull
#define CU Con ull
ull x,y;
public:
I Hash() {x=y=0;}I Hash(CU a):x(a),y(a){}I Hash(CU a,CU b):x(a),y(b){}
I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);}
I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);}
I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;}
I bool operator != (Con Hash& o) Con {return x^o.x||y^o.y;}
}seed,pw[N+5];
struct node {int Sz,S[2];Hash H;}O[N*LN*10];
I int Chk(CI tl,CI tr,CI l,CI r,CI rt)//求出区间内1的个数,用于检验二分
{
if(!rt||tl<=l&&r<=tr) return O[rt].Sz;RI mid=l+r>>1;
return (tl<=mid?Chk(tl,tr,L):0)+(tr>mid?Chk(tl,tr,R):0);
}
I int Find(CI rt,CI x)//二分,找出高于或等于当前位的最低的为0的位
{
RI l=x,r=N,t;W(l<r) Chk(x,t=l+r-1>>1,0,N,rt)<=t-x?r=t:l=t+1;//二分
return r;
}
I void Upt0(CI tl,CI tr,CI l,CI r,int& rt)//区间赋值为0
{
if(O[++tot]=O[rt],rt=tot,tl<=l&&r<=tr) return (void)(rt=0);RI mid=l+r>>1;
tl<=mid&&(Upt0(tl,tr,L),0),tr>mid&&(Upt0(tl,tr,R),0),PU(rt);
}
I void Upt1(CI t,CI l,CI r,int& rt)//单点赋值为1
{
if(O[++tot]=O[rt],rt=tot,l==r) return (void)(O[rt].Sz=1,O[rt].H=pw[l]);
RI mid=l+r>>1;t<=mid?Upt1(t,L):Upt1(t,R),PU(rt);
}
I bool le(CI l,CI r,CI rt1,CI rt2)//比大小
{
if(l==r) return O[rt1].Sz<O[rt2].Sz;RI mid=l+r>>1;//如果为叶节点,直接比较
if(!O[O[rt1].S[1]].Sz&&O[O[rt2].S[1]].Sz) return true;//如果rt1右节点内无1,而rt2内有,说明rt2大
if(O[O[rt1].S[1]].Sz&&!O[O[rt2].S[1]].Sz) return false;//如果rt2右节点内无1,而rt1内有,说明rt1大
if(O[O[rt1].S[1]].H!=O[O[rt2].S[1]].H) return le(mid+1,r,O[rt1].S[1],O[rt2].S[1]);//如果哈希值不同,比较右子树
if(!O[O[rt1].S[0]].Sz) return true;if(!O[O[rt2].S[0]].Sz) return false;
return le(l,mid,O[rt1].S[0],O[rt2].S[0]);//比较左子树
}
public:
int Rt[N+5];
I ChairmanTree()//初始化
{
p[0]=1,pw[0]=Hash(1,1),seed=Hash(233333,456789);
for(RI i=1;i<=N;++i) p[i]=(p[i-1]<<1)%X,pw[i]=pw[i-1]*seed;
}
I int Add(CI k,CI x)//求加上2^x后的和
{
RI t=Find(k,x),w=k;x^t&&(Upt0(x,t-1,0,N,w),0);//找到高于或等于当前位的最低的为0的位后,区间赋值为0
return Upt1(t,0,N,w),w;//单点赋值为1
}
I bool Less(CI k1,CI k2) {return le(0,N,k1,k2);}//比大小
I int GV(CI l,CI r,CI rt)//求出这个二进制数转化为十进制后的值
{
if(!rt||l==r) return O[rt].Sz?p[l]:0;RI mid=l+r>>1;
return (GV(L)+GV(R))%X;
}
#undef L
#undef R
#undef PU
}C;
int did[N+5];
class Dijkstra//最短路
{
private:
int vis[N+5],lst[N+5],cnt[N+5],St[N+5];
class SegmentTree//线段树优化
{
private:
#define P CI l=1,CI r=n,CI rt=1
#define L l,mid,rt<<1
#define R mid+1,r,rt<<1|1
#define mp make_pair
#define fir first
#define sec second
typedef pair<int,int> Pr;Pr V[N<<2];
I void PU(CI x)//上传信息
{
if(!~V[x<<1].fir) return (void)(V[x]=V[x<<1|1]);
if(!~V[x<<1|1].fir) return (void)(V[x]=V[x<<1]);
V[x]=V[C.Less(V[x<<1].fir,V[x<<1|1].fir)?x<<1:x<<1|1];
}
public:
I void Build(P)//建树,初始化全为-1
{
if(l==r) return (void)(V[rt]=mp(-1,l));RI mid=l+r>>1;
Build(L),Build(R),PU(rt);
}
I void Upt(CI x,CI v,P)//修改
{
if(l==r) return (void)(V[rt].fir=v);RI mid=l+r>>1;
x<=mid?Upt(x,v,L):Upt(x,v,R),PU(rt);
}
I int Query() {return ~V[1].fir?V[1].sec:-1;}//询问最小值
}S;
public:
I void Solve()//求解最短路
{
RI i,k,f,T=0;S.Build(),did[s]=1,S.Upt(s,0);
W(~(k=S.Query()))
{
for(S.Upt(k,-1),i=lnk[k];i;i=e[i].nxt) !vis[e[i].to]&&
(
f=C.Add(C.Rt[k],e[i].val),(!did[e[i].to]||C.Less(f,C.Rt[e[i].to]))&&
(did[e[i].to]=1,lst[e[i].to]=k,C.Rt[e[i].to]=f,S.Upt(e[i].to,C.Rt[e[i].to]),0)
);
}
if(!did[t]) puts("-1"),exit(0);k=t;W(St[++T]=k,k^s) k=lst[k];//判无解,求路径
F.write(C.GV(0,N,C.Rt[t]),'\n'),F.write(T,'\n');W(T) F.write(St[T--],' ');//输出最终答案
}
}D;
int main()
{
RI i,x,y,z;for(F.read(n,m),i=1;i<=m;++i) F.read(x,y,z),add(x,y,z),add(y,x,z);F.read(s,t);//读入,建边
return D.Solve(),F.clear(),0;
}

【CF464E】The Classic Problem(主席树+最短路)的更多相关文章

  1. Codeforces 464E #265 (Div. 1) E. The Classic Problem 主席树+Hash

    E. The Classic Problem http://codeforces.com/problemset/problem/464/E 题意:给你一张无向带权图,求S-T的最短路,并输出路径.边权 ...

  2. BZOJ 3218 UOJ #77 A+B Problem (主席树、最小割)

    大名鼎鼎的A+B Problem, 主席树优化最小割-- 调题死活调不对,一怒之下改了一种写法交上去A了,但是改写法之后第4,5个点常数变大很多,于是喜提UOJ全站倒数第三 目前还不知道原来的写法为什 ...

  3. Codeforces 464E The Classic Problem(主席树+最短路+哈希,神仙题)

    题目链接 题意:给出一张 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边连接 \(u_i,v_i\),边权为 \(2^{w_i}\),求 \(s\) 到 \(t\) 的最短路. \( ...

  4. 51Nod1863 Travel 主席树 最短路 Dijkstra 哈希

    原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1863.html 题目传送门 - 51Nod1863 题意 有 n 个城市,有 m 条双向路径连通它们 ...

  5. 【题解】BZOJ3489 A Hard RMQ problem(主席树套主席树)

    [题解]A simple RMQ problem 占坑,免得咕咕咕了,争取在2h内写出代码 upd:由于博主太菜而且硬是要用指针写两个主席树,所以延后2hQAQ upd:由于博主太菜而且太懒所以他决定 ...

  6. bzoj 3489 A simple rmq problem——主席树套线段树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3489 题解:http://www.itdaan.com/blog/2017/11/24/9b ...

  7. BZOJ.3489.A simple rmq problem(主席树 Heap)

    题目链接 当时没用markdown写,可能看起来比较难受...可以复制到别的地方看比如DevC++. \(Description\) 给定一个长为n的序列,多次询问[l,r]中最大的只出现一次的数.强 ...

  8. bzoj 3585: mex && 3339: Rmq Problem -- 主席树

    3585: mex Time Limit: 20 Sec  Memory Limit: 128 MB Description 有一个长度为n的数组{a1,a2,...,an}.m次询问,每次询问一个区 ...

  9. bzoj 3489 A simple rmq problem —— 主席树套线段树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3489 题解:http://www.itdaan.com/blog/2017/11/24/9b ...

随机推荐

  1. Pytorch的tensor数据类型

    基本类型 torch.Tensor是一种包含单一数据类型元素的多维矩阵. Torch定义了七种CPU tensor类型和八种GPU tensor类型: Data tyoe CPU tensor GPU ...

  2. 数据库——SQL SERVER Transact-SQL 程序设计

    什么是Transact-SQL? 标准SQL不支持过程化控制, 不能完成复杂的功能.T-SQL是过程化SQL语言,   是SQL的扩展 增加了过程化语句 (变量,赋值,分支,循环...)是数据库服务器 ...

  3. Revit二次开发 屏蔽复制构件产生的重复类型提示窗

    做了很久码农,也没个写博客的习惯,这次开始第一次写博客. 这个问题也是折腾了我接近一天时间,网上也没有任何的相关博文,于是决定分享一下,以供同样拥有此问题的小伙伴们参考. 内容源于目前在做的一个项目, ...

  4. centos 7 搭建vsftp

    一.FTP简介 1.ftp 概述                                                FTP:(file  transfer  protocol文件传输协议) ...

  5. liunx简单命令

    mysql -h主机地址 -u用户名 -p用户密码 --进入数据库1.显示数据库列表. show databases; 2.显示库中的数据表: use mysql: //打开库 show tables ...

  6. Python【day 17】面向对象-成员

    类的变量分成2种: 1.成员变量 概念:在构造方法中的变量,前面带有self 作用:可以在类中不同的方法间使用 2.类变量-静态变量 概念:在类中,构造方法和普通方法之外,定义的变量 作用: 1.调用 ...

  7. Javase之集合体系(3)之Set及其子类知识

    集合体系之Set及其子类知识 Set(接口) public interface Set<E>extends Collection<E> ​ 特点:无序(存储顺序与取出顺序不一致 ...

  8. django2-登录与出版社

    1.django核心功能 因为django功能很多 ,出版社可以使用到部分功能,最快最简单了解django的运行模式,每个点后续细化去梳理 django的路由 django的视图 django的模板 ...

  9. ECAMScript中的let和const

    let与const命令都是用来在js中声明变量的,在使用上与var相似,但存在一些区别,下面先讲let同var的区别 let 怎么使用呢,同var一样,let  变量名   就可以声明了 区别一:其作 ...

  10. 为Dynamics 365 USD设置使用Chrome进程来驻留Web应用程序

    我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...