[除一波最短路的草]

以前感觉Dijkstra没jb用 今天做了几道题感觉到了一点用处 首先他是在处理密集的图的时候比Spfa会快一点

时间是O(Nlogn)好像 然后有一道题Spfa跑了一分钟Dijkstra 0.1s 忘了什么题

所以Dijksta还是有点用的

 

其实在这里只想讲一点Dijkstra小的优化而已 并没有打算讲太多东西

1.用堆优化 sb都会写不会看白书(巫泽俊写的) P102-P103

2.其实这个优化不知道Spfa可不可以用 好像也可以,像往常一样 先给道例题 bzoj2200

这道题有单向边 有双向边 然后双向边是可以构成很多个联通块 单向边是连接联通块的单向边(无环)

 

直接草 抱歉超时 慢很多倍 那么我们可不可以考虑一个个联通块草 使得整个堆变小

 

这样的话我们先把双向边缩成联通块后 整个图就变成了DAG

然后按照拓扑序将联通块一块块合并起来 怎么说好呢 不算是合并 也就是一块块去做这个意思咯

对于单向边连的两块 影响就一个点 立即更新一下新联通块的点 但是很有可能这两块有很多个单向边连起来

然后再去做新的联通块 当然一开始有些联通块去不了的 你照样放进跑就好 反正也跑不了多少

但是有起点的联通块不一定入度不为0 所以的话 有起点的联通块 一定要从起点开始往外扫 不能从连入这个联通块的点扫(没意义)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#define Maxn 26000
using namespace std;
struct node
{
  int x,y,d,next;
}edge[Maxn*10]; int len,first[Maxn];
void ins(int x,int y,int d)
{
  len++; edge[len].x=x; edge[len].y=y; edge[len].d=d; edge[len].next=first[x]; first[x]=len;
}
int N,R,P,ST;

priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >Q;
queue<int>S;

int dis[Maxn];

int fa[Maxn]; int Find(int x){if(x==fa[x]) return fa[x]; else return fa[x]=Find(fa[x]);}
int Hash[Maxn]; int cnt;

int X[Maxn*2],Y[Maxn*2],D[Maxn*2];
vector<int>V[Maxn];
vector<int>BL[Maxn];

int ru[Maxn];
int main()
{
  freopen("roadplane.in","r",stdin);
  freopen("roadplane.out","w",stdout);
  scanf("%d%d%d%d",&N,&R,&P,&ST); len=0; memset(first,-1,sizeof(first));
  
  for(int i=1;i<=N;i++) fa[i]=i;
  for(int i=1;i<=R;i++)
  {
    int x,y,d; scanf("%d%d%d",&x,&y,&d); ins(x,y,d); ins(y,x,d);
    fa[Find(x)]=Find(y);
  }
  for(int i=1;i<=N;i++) if(fa[i]==i) Hash[i]=++cnt;
  for(int i=1;i<=N;i++) fa[i]=Find(fa[i]);
  
  for(int i=1;i<=P;i++)
  {
    scanf("%d%d%d",&X[i],&Y[i],&D[i]);
    V[Hash[fa[X[i]]]].push_back(i); ru[Hash[fa[Y[i]]]]++;
    
    BL[Hash[fa[Y[i]]]].push_back(Y[i]);
  }
  
  memset(dis,63,sizeof(dis)); dis[ST]=0; while(!S.empty()) S.pop();
  for(int i=1;i<=cnt;i++) if(ru[i]==0) S.push(i);
  for(int i=1;i<=N;i++) if(fa[i]==i&&ru[Hash[fa[i]]]==0) BL[Hash[fa[i]]].push_back(i); 
  BL[Hash[fa[ST]]].push_back(ST);
  while(!S.empty())
  {
    int now=S.front(); S.pop();
    for(int i=0;i<BL[now].size();i++)
    {
      int p=BL[now][i];
      Q.push(make_pair(dis[BL[now][i]],BL[now][i]));
    }
    while(!Q.empty())
    {
      pair<int,int> x=Q.top(); Q.pop();
      if(dis[x.second]<x.first) continue;
      for(int k=first[x.second];k!=-1;k=edge[k].next)
      {
        int y=edge[k].y;
        if(dis[y]>dis[x.second]+edge[k].d)
        {
          dis[y]=dis[x.second]+edge[k].d;
          Q.push(make_pair(dis[y],y));
        }
      }
    }
    
    for(int i=0;i<V[now].size();i++)
    {
      int p=V[now][i]; ru[Hash[fa[Y[V[now][i]]]]]--;
      
      if(ru[Hash[fa[Y[V[now][i]]]]]==0) S.push(Hash[fa[Y[V[now][i]]]]);

      dis[Y[V[now][i]]]=min(dis[Y[V[now][i]]],dis[X[V[now][i]]]+D[V[now][i]]);
    }
  }
  
  
  for(int i=1;i<=N;i++)
  {
    if(dis[i]<99999999) printf("%d\n",dis[i]);
    else printf("NO PATH\n");
  }
  return 0;
}
/*
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
*/
View Code

 

然后还有一些题是最短路导航在线改边的(不是你们想象的在线 而是走一步路 最短路就更改)

这种题从终点扫过来就好 很难讲自己意会 例题bzoj 3538

 

此短路的话就多一个数组记录一下 上次的次短路加上这条边? 最短路被更新时是否次短路被更新? 最短路没被更新次短路会被更新?

想一下就好 我也是自己YY的 可能会想多了情况 我不负责 例题bzoj 1726

 

还有最近超热门的分层图 强烈要求分层图要用Dijkstra+Heap写 不然吃屎我不负责 例题bzoj 1579

 

求各点间 路径和 加上 路径上的点权的最大值?  Floyd简单O(N^4)可以写 就是枚举点权最大值 其实还有O(N^3)的写法

就是把点按照点权从小到大排 然后每一次k枚举的都是点权从小到大的点 那么你i到j最大值肯定是i或者j或者k 而以k作跳板 也就是F[i][k] F[k][j]这些路径中的点的最大值一定不会超过当前的点权(除两个端点除外) 例题bzoj 1774

 

还有一些反人类的floyd的题 边数比点数大... 就按边的两端的点离散化一下做就好了 其他都没意义 例题bzoj 1706

 

还有更反人类的 边数比点数大 单向边 然后还一定过某些点 求两点间最短距离之类的 一定过某些点的某些点个数不多 所以关于这些点的正的扫一遍 反着建边扫一遍 然后就知道这些点到其它点的距离 然后枚举中间点就好了 例题bzoj 4093

 

还有一些比较烦人的负环问题 白书上写的用Bellman-Ford来解决 其实我解决有两种办法

1.在SPFA上判断一个点有没有进入N次 这个很容易证 进入N次就代表有负环了

2.用DFS判断负环如果这个点DFS到一个已经进栈的点 那就有问题了

void Dfs(int x)
{
  mark[x]=true;
  for(int k=first[x];k!=-1;k=edge[k].next)
  {
    int y=edge[k].y;
    if(dis[y]>=dis[x]+edge[k].d)
    {
      if(mark[y]){Flag=1; return ;}
      else
      {
        dis[y]=dis[x]+edge[k].d;
        Dfs(y);
      }
    }
  }
  mark[x]=false;
}

for(int i=1;i<=N;i++) {Dfs(i); if(Flag) return 1;}

这种方法好像比较快比较爽 但是还是习惯了用第一种

 

还有一些比较新的题 也就是分数规划 什么是分数规划 也就是说 题目要经过的点权和边权的比最小 那么就用到了

首先二分比值 然后把点权弄在边上 用比值乘于边权 也就是说现在的边权就是:(点权-边权*k) (自己写一写移项可得)

这样我们发现 当跑出来大于等于0是 k是合法的 不然不合法 但是我们很难判断是不是有合法 所以我们把边权取反 如果有一个负环 就证明有一条路是合法的

例题bzoj 1690

 

还有就是 其实最短路和差分约束系统是很相近的 他们有共同的东西 简单提一下

其实我们要化成A+C>=B (C是常数) 也就是说B比A最多大C 这样选最小的c 就是约束了

我只讲了点片面重点的 具体百度

 

A1-A0<=C 那么就可以移项变成 A0 -> A1 花费为C

A1-A0>=C A1->A0 花费为-C

然后画图 分类讨论一下就好了 例题bzoj 1731

 

再给一道比较妙的题 bzoj1698

spfa最短路径次数直接跑是不行的 原因有如下:

1.有1到1的情况 也就是先从一个1走到另一个1 再从另一个1走到1 代价不会变

所以这样的话我就想到了把荷叶变成联通块判断这种情况 但是还有..

2.打个比方:

0 0 y 0 0

1 0 0 0 1

0 0 x 0 0

x和y都是清水 从x去到y 可以经过左边的联通块去 可以经过右边的联通块去 答案有两种但其实就只有x,y放荷叶一种

这样处理想起来要解决其实很麻烦 所以我不会去看题解了

题解很简单 先想想 就是x到y所有路径之间都是1  但是不同的联通块 那么我们换位思考 那些不同的联通块连的荷叶点是不是都可以互相到达 然后花费为1

答案是的 所以的话 我们忽略联通块 就把联通块所能去到的荷叶点连起来就好了 但是这样连注意判重

其实下次看到这种题要想想那些不需要的可不可以删掉 来把图变得简单一点 

 

还有一些跨点建边的 也就是A->B B->C A可以到达C且免费 这样跑偷懒最多可以K次 这道题是亲身经历的 GDOI2015 Day2 第一题 当时随便草了个分成图 然后还上去讲了 最后只有30分..

就是因为边目录的时候不可以直接A->C  不然就会以C为跳板去到 A->C  C->D  A->D...

这个点 打边目录的孩子注意以下就好了 印象深刻 处理方法就是判断这一条边是不是新搞过出来的 其实还有一种特殊的建边方式 可以省边 但是我忘了 可以自己上网搜脑补

 

最后的话总结一下 就是判负环 DAG拓扑优化 任意两点边权加点权最小值或者最大值 分数规划 差分约束 分层图 在线导航 几乎就是这些了最短路

 

我说完了

如果你按照这份题表做完了 你会发现 我草我在带你刷bzoj水题 嘻嘻 所以刷完这份题表后记得评论一个 让我知道一下哪些人这么聪明跟我一起刷水题

有问题想题解仔细的也评论就好了

 

posted @ 2016-10-18 21:36  wohenshuai  阅读(314)  评论(5编辑  收藏  举报