点此看题面

大致题意: 给你一张无向图,每条边的边权为\(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. 数据嵌入js的关系图

    参照echarts官网,改了一下效果图: 数据放在了js里. 代码: <%@ page language="java" contentType="text/html ...

  2. MySQL InnoDB 索引 (INDEX) 页结构

    MySQL InnoDB 索引 (INDEX) 页结构 InnoDB 为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做索引页 索引页内容 索引页分为以下部分: File Header:表 ...

  3. Java连载47-多态基础语法、作用

    一.多态的语法 1.两个类之间没有继承关系的,使用多态是不能编译的. 2.无论向上还是向上转型,都需要有继承关系. 3.什么时候需要向下转型? 当调用的方法或者属性是子类型特有的,在父类型中不存在,就 ...

  4. Java的23种设计模式,详细讲解(二)

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  5. hibernate关联关系(多对多)

    数据库的多对多数据库中不能直接映射多对多 处理:创建一个桥接表(中间表),将一个多对多关系转换成两个一对多 注:数据库多表联接查询 永远就是二个表的联接查询 注2:交叉连接 注3:外连接:left(左 ...

  6. Python3---AJAX---爬虫

    前言 该文章主要介绍面对AJAX的网页如何爬去信息,主要作用是适合刚入门爬虫查看学习 修改时间:20191219 天象独行 首先,我们先介绍一下什么是AJAX,AJAX是与服务器交换数据并跟新部分网页 ...

  7. JAVA的基本语法1

    1.关键字 关键字的定义和特点 定义:被JAVA语言赋予了特殊含义,用作专门用途的字符串(单词). 就是在java语言编程的时候,在关键的地方使用的单词,体现关键的地方的含义.这些单词都是特有的,并且 ...

  8. 性能篇系列—stream详解

    Stream API Java 8集合中的Stream相当于高级版的Iterator Stream API通过Lambda表达式对集合进行各种非常便利高效的聚合操作,或者大批量数据操作 Stream的 ...

  9. linux上文件挂载的案例

    cat /etc/fstab 将172.20.20.117上的172.20.20.117:/data/nfs/zichan/目录挂载到172.20.20.112机器上,其实类似目录共享 在需要挂载的机 ...

  10. UITableViewStyleGrouped 类型 tableView sectionHeader 高度问题

    UITableViewStyleGrouped 类型的 tableView 在适配的时候出现很大的问题.记录一下 按照之前的方法,只需要执行以下的代码就能够很好的解决 section == 0 的时候 ...