HDU5669 Road 分层最短路+线段树建图

分析:(官方题解)

首先考虑暴力,显然可以直接每次O(n^2)

?的连边,最后跑一次分层图最短路就行了.

然后我们考虑优化一下这个连边的过程 ,因为都是区间上的操作,所以能够很明显的想到利用线段树来维护整个图,

连边时候找到对应区间,把线段树的节点之间连边.这样可以大大缩减边的规模,然后再跑分层图最短路就可以了.

但是这样建图,每一次加边都要在O(logn)个线段树节点上加边,虽然跑的非常快,但是复杂度仍然是不科学的.

为了解决边的规模的问题,开两棵线段树,连边时候可以新建一个中间节点,在对应区间的节点和中间节点之间连边

进一步缩减了边的规模,强行下降一个数量级

最后跑一下分层图最短路就行了

复杂度O(mlog^2n)

什么你会线段树但是不会分层图最短路?安利JLOI2011飞行路线.

因为边的数目还是相对比较多的,所以不能使用SPFA,而要使用Heap-Dijkstra来做最短路,

但是不排除某些厉害的选手有特殊的SPFA姿势可以做或者普通 SPFA写的比较优美就不小心跑过去了...

注:出题人的题解写的很详细了,然后JLOI2011飞行路线是BZOJ2763 直接去做就好了

然后我的建图刚开始不太完善,跑了600+ms,然后后来完善了一下,按线段树节点建图(这就是题解)

不过每个线段树的节点不需要和它的区域内所有的点连边,只需要按照线段树的结构,连它的左右儿子就行了

代码:

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <string.h>
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=5e4+1;
int d[11][N*10+20],head[N*10+20],tot,n,m,k;
struct Edge{
  int v,w,next;
};
vector<Edge>edge;
void add(int u,int v,int w){
  edge.push_back(Edge{v,w,head[u]});
  head[u]=edge.size()-1;
}
struct Node{
  int cur,v,dis;
  bool operator<(const Node &rhs)const{
    return dis>rhs.dis;
  }
};
priority_queue<Node>q;
bool vis[11][N*10+20];
int dij(){
  memset(d,INF,sizeof(d));
  memset(vis,0,sizeof(vis));
  d[0][8*N+1]=0;
  q.push(Node{0,8*N+1,0});
  while(!q.empty()){
    int cur=q.top().cur,u=q.top().v;
    q.pop();
    if(vis[cur][u])continue;
    vis[cur][u]=1;
    for(int i=head[u];~i;i=edge[i].next){
       int v=edge[i].v;
       if(!vis[cur][v]&&d[cur][v]>d[cur][u]+edge[i].w){
        d[cur][v]=d[cur][u]+edge[i].w;
        q.push(Node{cur,v,d[cur][v]});
       }
       if(cur+1>k)continue;
       if(!vis[cur+1][v]&&d[cur+1][v]>d[cur][u]){
         d[cur+1][v]=d[cur][u];
         q.push(Node{cur+1,v,d[cur+1][v]});
       }
    }
  }
  return d[k][8*N+n]==INF?-1:d[k][8*N+n];
}
void build(int rt,int l,int r){
  if(l==r){
    add(N*8+l,rt,0);
    add(rt+4*N,N*8+l,0);
    return;
  }
  int mid=(l+r)>>1;
  build(rt<<1,l,mid);
  build(rt<<1|1,mid+1,r);
  add(rt<<1,rt,0),add(rt<<1|1,rt,0);
  add(rt+4*N,4*N+rt*2,0),add(rt+4*N,4*N+rt*2+1,0);
}
int now,w;
void treeadd1(int rt,int l,int r,int x,int y){
   if(x<=l&&r<=y){
      add(rt,now,w);
      return;
   }
   int mid=(l+r)>>1;
   if(x<=mid)treeadd1(rt<<1,l,mid,x,y);
   if(y>mid)treeadd1(rt<<1|1,mid+1,r,x,y);
}
void treeadd2(int rt,int l,int r,int x,int y){
   if(x<=l&&r<=y){
      add(now,rt+4*N,0);
      return;
   }
   int mid=(l+r)>>1;
   if(x<=mid)treeadd2(rt<<1,l,mid,x,y);
   if(y>mid)treeadd2(rt<<1|1,mid+1,r,x,y);
}
int main(){
  scanf("%d%d%d%d",&n,&n,&m,&k);
  memset(head,-1,sizeof(head));
  edge.clear();
  build(1,1,n);
  now=9*N;
  for(int i=1;i<=m;++i){
    int a,b,c,d;
    scanf("%d%d%d%d%d",&a,&b,&c,&d,&w);
    ++now;
    treeadd1(1,1,n,a,b);
    treeadd2(1,1,n,c,d);
    ++now;
    treeadd1(1,1,n,c,d);
    treeadd2(1,1,n,a,b);
  }
  int ans=dij();
  if(ans==-1)printf("CreationAugust is a sb!\n");
  else printf("%d\n",ans);
  return 0;
}

时间: 2024-08-11 09:52:46

HDU5669 Road 分层最短路+线段树建图的相关文章

POJ 1436 Horizontally Visible Segments(线段树建图+枚举)

题目连接:http://poj.org/problem?id=1436 题意:给一些线段,每个线段有三个值y1, y2, x代表起点为(x, y1),终点为(x, y2)的线段.当从一个线段可以作水平线到另一个线段并且不穿过其他线段时,就称这两个线段时水平可见的.当三个线段可以两两水平可见,就称为形成一个线段三角.问:在这些线段中有多少个这样的线段三角? 分析:可以把每条线段看做是一个点,如果它和其他线段是水平可见的,就将这两个点相连,由于是无向图,就是你能看到我,我也能看到你,所以需要连接两次

线段树建图

对于一个图,n个节点,有向边,求点s到其他所有点的最短路. 题目给的边的方式: u -> v [l,r] -> v v -> [l,r] 这样的话边数是O(n^2)级别的,怎么做? 假设把[1,n]建成segment tree 后,有tot个节点. 则建一个新图,新图有2 * tot + n个节点 新图多了2 * tot个节点,表示2棵线段树A,B 点的编号分类: A - [1,tot] B - [tot + 1,2 * tot] C - [2 * tot + 1,2 * tot + n

[线段树建图 bfs] BZOJ 3073 [Pa2011]Journeys

CA爷的题解:http://blog.csdn.net/CreationAugust/article/details/50739132 #include<cstdio> #include<cstdlib> #include<algorithm> #include<set> using namespace std; inline char nc() { static char buf[100000],*p1=buf,*p2=buf; if (p1==p2) {

CF-787D-线段树建图+最短路

http://codeforces.com/problemset/problem/787/D 题目大意是给出一个有向图,有N个节点,初始节点在S,询问S到所有点最短路.边的读入方式有三种, 1 u v w  表示 u->v有一条边权为w的边, 2 v l r w ,表示v->[l,r]内的任意一个点支付w即可, 3 v l r w 表示从[l,r]内任意一个点到v支付w即可.直接构图的话可能会出现完全图,被卡死. 一种巧妙的构图方式是,由这些个区间联想到线段树(然而我并没有想到),我们不妨对2

BZOJ 2725: [Violet 6]故乡的梦 最短路+线段树

2725: [Violet 6]故乡的梦 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 678  Solved: 204[Submit][Status][Discuss] Description Input Output Sample Input 6 7 1 2 1 2 3 1 3 4 2 4 5 1 5 6 1 1 3 3 4 6 3 1 6 4 1 2 1 3 4 3 6 5 Sample Output 7 6 Infinity 7 HINT

hdu3499(分层图最短路 or 反向建图)

传送门 方法一:分层图 #include<bits/stdc++.h> #define per(i,a,b) for(int i=a;i<=b;i++) #define mod 1000000007 using namespace std; typedef long long ll; const ll inf =23333333333333333LL; const double eps=1e-8; int T; int read(){ char ch=getchar(); int res

BZOJ 1018 线段树维护图的连通性问题

思路: 我们可以搞一棵线段树 对于一段区间有6种情况需要讨论 左上右下.左上右上.左下右下.左下右上 这四种比较好维护 用左上右下举个例子吧 就是左儿子的左上右下&左区间到右区间下面有路&右儿子的左下右下 或者是左儿子的左上右上&左区间到右区间上面有路&右儿子的左上右下 还有两种  区间的左(右)端点上下能不能联通 需要维护 这种就是左儿子的上下连通或(左上右上&左上右下&左到右两条路都联通&右儿子的上下联通) (假设c1<c2) 最后要查的是

BZOJ 1018 线段树维护图连通性

用8个bool维护即可分别为LURU,LURD,LDRU,LDRD,LULD,RURD,Side[1],Side[2]即可. Side表示这一块有没有接到右边.Merge一下就可以了.码农题,WA了一次,发现未初始化,就AC了.. 1 #include <cstdio> 2 inline int Min(int x,int y) {return x>y?y:x;} 3 inline void Swap(int &x,int &y) {int t=x;x=y;y=t;} 4

[CF787D]遗产(Legacy)-线段树-优化Dijkstra(内含数据生成器)

Problem 遗产 题目大意 给出一个带权有向图,有三种操作: 1.u->v添加一条权值为w的边 2.区间[l,r]->v添加权值为w的边 3.v->区间[l,r]添加权值为w的边 求st点到每个点的最短路 Solution 首先我们思考到,若是每次对于l,r区间内的每一个点都执行一次加边操作,不仅耗时还耗空间. 那么我们要想到一个办法去优化它.一看到lr区间,我们就会想到线段树对吧. 没错啦这题就是用线段树去优化它. 首先我们建一棵线段树,然后很容易想到,我们只需要把这一棵线段树当做