【CF464E】The Classic Problem(主席树+最短路)
大致题意: 给你一张无向图,每条边的边权为\(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(主席树+最短路)的更多相关文章
- Codeforces 464E #265 (Div. 1) E. The Classic Problem 主席树+Hash
E. The Classic Problem http://codeforces.com/problemset/problem/464/E 题意:给你一张无向带权图,求S-T的最短路,并输出路径.边权 ...
- BZOJ 3218 UOJ #77 A+B Problem (主席树、最小割)
大名鼎鼎的A+B Problem, 主席树优化最小割-- 调题死活调不对,一怒之下改了一种写法交上去A了,但是改写法之后第4,5个点常数变大很多,于是喜提UOJ全站倒数第三 目前还不知道原来的写法为什 ...
- Codeforces 464E The Classic Problem(主席树+最短路+哈希,神仙题)
题目链接 题意:给出一张 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边连接 \(u_i,v_i\),边权为 \(2^{w_i}\),求 \(s\) 到 \(t\) 的最短路. \( ...
- 51Nod1863 Travel 主席树 最短路 Dijkstra 哈希
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1863.html 题目传送门 - 51Nod1863 题意 有 n 个城市,有 m 条双向路径连通它们 ...
- 【题解】BZOJ3489 A Hard RMQ problem(主席树套主席树)
[题解]A simple RMQ problem 占坑,免得咕咕咕了,争取在2h内写出代码 upd:由于博主太菜而且硬是要用指针写两个主席树,所以延后2hQAQ upd:由于博主太菜而且太懒所以他决定 ...
- bzoj 3489 A simple rmq problem——主席树套线段树
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3489 题解:http://www.itdaan.com/blog/2017/11/24/9b ...
- BZOJ.3489.A simple rmq problem(主席树 Heap)
题目链接 当时没用markdown写,可能看起来比较难受...可以复制到别的地方看比如DevC++. \(Description\) 给定一个长为n的序列,多次询问[l,r]中最大的只出现一次的数.强 ...
- bzoj 3585: mex && 3339: Rmq Problem -- 主席树
3585: mex Time Limit: 20 Sec Memory Limit: 128 MB Description 有一个长度为n的数组{a1,a2,...,an}.m次询问,每次询问一个区 ...
- bzoj 3489 A simple rmq problem —— 主席树套线段树
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3489 题解:http://www.itdaan.com/blog/2017/11/24/9b ...
随机推荐
- PHP中生成随机字符串,数字+大小写字母随机组合
简单的生成随机字符串: /* * 生成随机字符串 * * $length 字符串长度 */ function random_str($length) { // 密码字符集,可任意添加你需要的字符 $c ...
- #3145. 「APIO 2019」桥梁
#3145. 「APIO 2019」桥梁 题目描述 圣彼得堡市内所有水路长度总和约 282 千米,市内水域面积占城市面积的 7%.--来自维基百科 圣彼得堡位于由 \(m\) 座桥梁连接而成的 \(n ...
- MySQL基础之Natural Join用法
Natural join即自然连接,natural join等同于inner join或inner using,其作用是将两个表中具有相同名称的列进行匹配 用https://www.w3resourc ...
- 11-scrapy(递归解析,post请求,日志等级,请求传参)
一.递归解析: 需求:将投诉_阳光热线问政平台中的投诉标题和状态网友以及时间爬取下来永久储存在数据库中 url:http://wz.sun0769.com/index.php/question/que ...
- C语言--计算程序执行时间
C语言–计算程序执行时间1. gettimeofday精度1us #include<stdio.h>#include<sys/time.h> int main(){ /* 定义 ...
- 面试必问的Spring IOC详解
广义的 IOC IoC(Inversion of Control) 控制反转,即“不用打电话过来,我们会打给你”. 两种实现: 依赖查找(DL)和依赖注入(DI). IOC 和 DI .DL 的关系( ...
- SmtpClient发送邮件时附件名称乱码
在用户环境发现一个现象,使用System.Net.Mail.SmtpClient发送邮件,当附件名包含中文且长度较长时,最终的邮件里附件名会乱掉,写个简单的测试程序: var mail = new M ...
- SPA项目开发之CRUD+表单验证
表单验证 Form组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则, 并将Form-Item的prop属性设置为需校验的字段名即可 <el-form-item label ...
- PHP+Ajax点击加载更多列表数据实例
一款简单实用的PHP+Ajax点击加载更多列表数据实例,实现原理:通过“更多”按钮向服务端发送Ajax请求,PHP根据分页参数查询将最新的几条记录,数据以JSON形式返回,前台Query解析JSON数 ...
- MIME格式解析
- 邮件例子 一个MIME格式的邮件例子如下: Return-Path: <mlemos@acm.org> To: Manuel Lemos <mlemos@linux.local& ...