Bzoj3073Journeys
这不裸的dij吗?来,弄他。
打完以后发现不妙,这数据范围略神奇……算一算,考一场都可能跑不出来。map去重边(成功额外引入log)不怕,交。TLE,54。
这不玩呢吗,把map去了,交。MLE,71……这题考场性价比可能挺高的。
尝试改成vector,没省内存,反而更慢了。
发现以前没学过的一个神奇知识点——线段树优化建图。
这东西,一般出现某个区间和另一个区间内的若干元素建边时使用,因为一个点一个点的建肯定Boom。
这时,根据我们对线段树的理解,我们可以把线段树上的节点当成图中的节点,跟据线段树上一个节点可以表示一个区间这个性质就可以加快建边了,剩下的就是跑一个dij的事。
具体一点,过程如下(你看完这段话肯能还得看看别的博客):
1.建立一颗线段树,叫做“出树”,代表离开一个节点的信息,出树的所有儿子建边指向父亲,边权为零,举个栗子,可以理解为,从1出去,也是从[1,2]中出去,也是从[1,4]中出去,同时没有额外的花费。
2.建立另一颗线段树,叫做“入树”,代表进入一个节点的信息,入树的所有父亲建边指向儿子,边权为零,举个核桃,可以理解为,进入1,也是进入[1,2],也是进入[1,4],同时没有花费。(当然你也可以不这么理解,因为容易混)。
3.在两颗线段树之间建边,入树的底层1指向出树的底层1,入树的底层2指向出树的底层2。可以理解为,进入这个节点后就可以离开这个节点,边权依旧为零。
4.修改,额外开一个新的节点作为中转站(博主试过开两个中间节点的打法,感觉有点奇怪,没试过不开中间节点的,感兴趣的读者可以试一试),把我们要修改的区间分成约log个小区间,和求区间和有点像,就是把[1,4]拆成[1,3]和4这种(自己去画画线段树吧),然后让出树的若干区间指向这个节点,同时带上边权,这道题就是1喽。然后让这个节点再次指向入树的若干区间,权值为零(这块可能也得好好理解一下),别忘了这可是双向边,倒过来在连一遍,就是重复操作4一次,记得新开点,权值给对(这么看来这个中转站好像就只是让代码好打了一些,没有什么实际意义,因为你直接连这两个节点也可以,但是大家可以自己脑补一下代码,可能略恐怖,当然,你是神犇你任性)。
剩下的就是dijkstra了,没学的自己去学,想看板子的给个小链接:最短路
自行理解吧,博主也得去在画画,思考思考。
代码里有个pos数组,是指原题某个数对应的线段树节点,也就是双向存储的另一方。
复杂度的话,网上有各种各样的版本,博主给个大概,边数最坏大约是O(6n+2mlogn),点数大约是O(6n+m),那么根据dijkstra的复杂度为O((n+m)logn)(一般n的规模不大于m时,近似成O(mlogn)) 这样把n代入边数,m代入点数,比原来的O(n*m^2)强了好多。
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
using namespace std;
struct EDGE{
int ed,nex,w;
}edge[];int first[],num;
struct node{
int id,num;
friend bool operator < (const node &a,const node &b){
return a.num>b.num;
}
}now;
int dis[];bool v[];
struct Sengment_Tree{
int ls,rs,l,r;
}tr[],tc[];//入树,出树。
int pos[],p1,p2,tot,root1,root2;
int n,m,P;
int read(){
int sum=,f=;char x=getchar();
while(x<''||x>''){
if(x=='-') f=-;
x=getchar();
}while(x>=''&&x<=''){
sum=sum*+x-'';
x=getchar();
}return sum*f;
}
void add(int st,int ed,int w){
// cout<<" st="<<st<<" ed="<<ed<<" w="<<w<<endl;
edge[++num].ed=ed;
edge[num].w=w;
edge[num].nex=first[st];
first[st]=num;
}
void buildtc(int &p,int l,int r){
p=++tot;
tc[p].l=l;tc[p].r=r;
if(l==r) return ;
int mid=l+r>>;
buildtc(tc[p].ls,l,mid);
buildtc(tc[p].rs,mid+,r);
add(tc[p].ls,p,);
add(tc[p].rs,p,);
}
void buildtr(int &p,int l,int r){
p=++tot;
tr[p].l=l;tr[p].r=r;
if(l==r){
pos[l]=p;
return ;
}
int mid=l+r>>; buildtr(tr[p].ls,l,mid);
buildtr(tr[p].rs,mid+,r);
add(p,tr[p].ls,);
add(p,tr[p].rs,);
}
void buildmid(int p){
if(tc[p].l==tc[p].r){
add(pos[tc[p].l],p,);
return ;
}
buildmid(tc[p].ls);
buildmid(tc[p].rs);
}
void changetc(int p,int l,int r){ if(l<=tc[p].l&&tc[p].r<=r){
add(p,p1,);
return;
}
int mid=tc[p].l+tc[p].r>>;
if(l<=mid) changetc(tc[p].ls,l,r);
if(r>mid) changetc(tc[p].rs,l,r);
}
void changetr(int p,int l,int r){
if(l<=tr[p].l&&tr[p].r<=r){
add(p1,p,);
return ;
}
int mid=tr[p].l+tr[p].r>>;
if(l<=mid) changetr(tr[p].ls,l,r);
if(r>mid) changetr(tr[p].rs,l,r);
}
void dijkstra(int st){
memset(dis,0x7f,sizeof(dis));
priority_queue<node>q;
dis[pos[st]]=;
now.id=pos[st];now.num=;
q.push(now);
while(!q.empty()){
now=q.top();q.pop();
int x=now.id;
if(v[x]) continue;
v[x]=;
for(int i=first[x];i;i=edge[i].nex){
int y=edge[i].ed;
if(dis[y]>dis[x]+edge[i].w){
dis[y]=dis[x]+edge[i].w;
now.id=y;now.num=dis[y];
q.push(now);
}
}
}
}
void change(int a,int b,int c,int d){
p1=++tot;
changetc(root1,a,b);
changetr(root2,c,d);
}
int main(){
/*freopen("11.in","r",stdin);
freopen("11.out","w",stdout);*/
n=read();m=read();P=read();
buildtc(root1,,n);buildtr(root2,,n);buildmid(root1);
for(int i=,a,b,c,d;i<=m;i++){
a=read();b=read();c=read();d=read();
change(a,b,c,d);
change(c,d,a,b);
}
dijkstra(P);
for(int i=;i<=n;i++)
printf("%d\n",dis[pos[i]]);
return ;
}
出树子指父,
入树父指儿。
横叉入指出,
新边指两边。
Bzoj3073Journeys的更多相关文章
- bzoj3073Journeys(线段树优化最短路)
这里还是一道涉及到区间连边的问题. 如果暴力去做,那么就会爆炸 那么这时候就需要线段树来优化了. 因为是双向边 所以需要两颗线段树来分别对应入边和出边 QwQ然后做就好了咯 不过需要注意的是,这个边数 ...
随机推荐
- electron-vue在npm run build时报错 ⨯ cannot execute cause=fork/exec C:\Users\801\AppData\Local\electron-builder\Cache\winCodeSign\winCodeSign-2.5.0\rcedit-ia32.exe: Access is denied.
问题描述 在electron-vue执行npm run build时报错,错误如下: ⨯ cannot execute cause=fork/exec C:\Users\801\AppData\Loc ...
- Express bodyParser中间件使用方式
bodyParser中间件用来解析http请求体,是express默认使用的中间件之一. 1.这个模块提供以下解析器 (1) JSON body parser (2) Raw body parser ...
- 第九章、import 和from ...import
目录 第九章.import 和from ...import 一.import和 from ...import ... 二.import模块名 第九章.import 和from ...import 一. ...
- 【转】make menuconfig/.config/Kconfig解析
当执行#make menuconfig时会出现内核的配置界面,所有配置工具都是通过读取"arch/$(ARCH)/Kconfig"文件来生成配置界面,这个文件就是所有配置的总入口, ...
- TP5通用化API接口数据封装
之前在做项目的时候,特别是最近总是再使用前后端分离模式,向客户端提供接口,每次都傻不拉几的 在每一个方法里面 写 $data = ['status'=>1,'message'=>'XXX' ...
- Hdu 2147 巴什博弈 PN图
P:先手必输 N:先手必胜 PN图规则:如果一个点能转换到的所有点都是N,那么此点状态为P.若能转化到一个P,即使对手面临必输状态,此点为N. 然后找规律就行 #include<bits/s ...
- Scrapy爬取小说简单逻辑
Scrapy爬取小说简单逻辑 一 准备工作 1)安装Python 2)安装PIP 3)安装scrapy 4)安装pywin32 5)安装VCForPython27.exe ........... 具体 ...
- 查看ocx控件CLSID的方法(转载)
CLSID就是classID类的标识码 1.打开注册表,window + r ,输入regedit,确定 2.点击 编辑 选择查找 3.ok拉 参考:https://blog.csdn.net/u01 ...
- git clone报错error: RPC failed; curl 18 transfer closed with outstanding read data remaining
具体错误信息如下图: error: RPC failed; curl 18 transfer closed with outstanding read data remaining fatal: ...
- LOAD DATA INFILE & mysqlimport
+++++++++++++++++++++++++++++++++++++++++++++mysqlimport++++++++++++++++++++++++++++++++++++++++++++ ...