poj2449

来源:互联网 发布:tv看电视直播软件 编辑:程序博客网 时间:2024/06/07 05:42

从前有一天,有个叫YZH的可爱小朋友见到了奇奇怪怪的牛蜀黍OZY

Created with Raphaël 2.1.0yzhyzhozyozy好无聊啊,不知道做什么题ozy yin笑一声,说:来来来我安利一道很(ju)好(nan)的题

五小时后,yzh在家里终于AC了这道题(不存在的)———的40%
(未完待续)如果对我心情有兴趣的话去看看bzoj4933我会写博客的

如你所见,这是一道k短路,适合新手的模版
两点:
1: 至少先会最短路。
2: 如果不会queue的人呢先去学学。

方法:
先将ed到所有点的距离求出来,做法:也就是以ed为起点去跑一次最短路嘛。
然后来一个看似高级的算法——A*算法。(不关搜索的事,用来优化最短路径问题的)
首先:queue有个优先队列的用法对吧,可以将值按小到大排序,这样一来我们就可以将spfa中的队列list按照g、f从小到大排。是不是像宽搜一样!?由于优先队列,当第一次找到ed的值就是最短路!!(至于g,f的定义我就不说了【懒】,看过其他题解的人都知道【欢乐】)

有人会看代码下面吗

#include<cstdio>#include<cstring>#include<queue>using namespace std;struct node{    int x,y,d,next;}a[510000],b[510000];int len1,last1[11000],len2,last2[11000]; struct As{    int f,g,v;//f==d[x]+g,用来维护优先队列 g表示出发走了多少距离,v相当于边目录的y     bool operator<(const As a)const    {        if(a.f==f)return a.g<g;        return a.f<f;    }};priority_queue<As>Q;void ins1(int x,int y,int d){    len1++;    a[len1].x=x;a[len1].y=y;a[len1].d=d;    a[len1].next=last1[x];last1[x]=len1;}void ins2(int x,int y,int d){    len2++;    b[len2].x=x;b[len2].y=y;b[len2].d=d;    b[len2].next=last2[x];last2[x]=len2;}int st,ed,k,d[11000],list[11000];bool v[11000];void SPFA(){    memset(d,63,sizeof(d));d[ed]=0;    memset(v,false,sizeof(v));v[ed]=true;    int head=1,tail=2;list[1]=ed;    while(head!=tail)    {        int x=list[head];        for(int k=last2[x];k;k=b[k].next)        {            int y=b[k].y;            if(d[y]>d[x]+b[k].d)            {                d[y]=d[x]+b[k].d;                if(v[y]==false)                {                    v[y]=true;                    list[tail]=y;                    tail++;if(tail==10000)tail=1;                }            }        }        v[x]=false;        head++;if(head==10000)head=1;    }}void A_star()  {      int cnt=0;As x,y;    x.v=st,x.g=0,x.f=x.g+d[st];Q.push(x);    while(Q.empty()==false)    {        x=Q.top();Q.pop();        if(x.v==ed)//由于优先队列,先找到的肯定是最好的,由此类推         {            cnt++;            if(cnt==k){printf("%d\n",x.g);return ;}        }        for(int k=last1[x.v];k;k=a[k].next)        {            y.v=a[k].y;            y.g=x.g+a[k].d;            y.f=y.g+d[y.v];            Q.push(y);        }    }    printf("-1\n");}  int main(){    int n,m,X,Y,D;    scanf("%d%d",&n,&m);    len1=0;memset(last1,0,sizeof(last1));    len2=0;memset(last2,0,sizeof(last2));    for(int i=1;i<=m;i++)    {        scanf("%d%d%d",&X,&Y,&D);        ins1(X,Y,D);ins2(Y,X,D);    }    scanf("%d%d%d",&st,&ed,&k);    SPFA();    if(st==ed)k++;    if(d[st]>999999999){printf("-1\n");return 0;}    A_star();    return 0;}

诶还有人翻下来啊真开心,没错,23个小时以后,我又学会了一种更强的方法。。此处应有OZY的yin笑 我们计算一下时间复杂度,A*需要O(nk)的时间,而下面的……O(nlogn+mlogm+klogk)!当k较大时我们可以使用这个东西——可持久化的堆来做,我用了左偏树。

1:这里我用了queue跑反图最短路(其实跟上面的一样的)
2:这个时候,我们要将图转化为最小路径树(就是从st出发,到某个点最短路,又从st出发,到另个点最短路,经过的点不重复,将所有的点都连起成一棵树,st就是根了)
3:然后关于左偏树(其实它也算堆)呢,可以先看看OZY大神的博客【懒】,然后将堆合并一下。
http://blog.csdn.net/qq_36797743/article/details/75151048
http://blog.csdn.net/qq_36797743/article/details/75163939

ozy曰:下文中 a[i].d-(d[a[i].y]-d[a[i].x])
这句话为什么这么写呢,那是因为,我们可以把 a[i].d-(d[a[i].y]-d[a[i].x])等价于之前有一条x到y的边,这个你可以感性地认识,然后呢,你选了这条边,其实就相当于代价增加了这么多

我的理解是d中是最优解,(d[a[i].y]-d[a[i].x])是最短路(不一定联通,可能经过其他点),a[i].d必然大于其,相当于换一个方案,所要增加的距离。其实我不是很懂不过感觉OZY这么厉害好像也说的很含糊肯定他觉得很简单我还是不问了免得被嘲讽
大家可以去看看俞鼎力《堆的可持久化与k短路》讲的很高深但思路应该差不多的,有兴趣的一起去做做bzoj4933吧【我要拖人下水】

#include<cstring>#include<algorithm>#include<iostream>#include<queue>using namespace std;typedef pair<int,int>P;int n,m,st,ed,K;struct node{    int x,y,d,next;}a[110000];int len,last[1100];void ins(int x,int y,int d){    len++;    a[len].x=x;a[len].y=y;a[len].d=d;    a[len].next=last[x];last[x]=len;}int d[1100],f[1100];//f记录最短路径树的边 void get_dis()//利用优先队列跑反图的最短路 {    priority_queue< P,vector<P>,greater<P> >q;//greater是令这个优先队列从小到大排     memset(f,0,sizeof(f));    memset(d,63,sizeof(d));d[ed]=0;    q.push(P(d[ed],ed));    while(q.empty()==false)    {        P t=q.top();q.pop();        int x=t.second;        if(t.first>d[x])continue;        for(int k=last[x];k;k=a[k].next)        {            int y=a[k].y;            if(d[y]>d[x]+a[k].d)            {                f[y]=k;                 d[y]=d[x]+a[k].d;                q.push(P(d[y],y));            }        }    }}bool b[110000];//这条边是不是一条树边int root[1100],tot;//root表示该点现在在那个堆中,tot当前给到的编号struct heap//其实这个堆也相当于一颗二叉树{    int l,r,d;    P v;//second是一个点,first是从该点出发到终点为满足k短路多花的距离 }h[2100000];//关于d(距离)的定义:假如在x的子树中的任意叶子节点插入一个点,该点到x的最短距离就是d int bt(P p)//为x点建一个堆 {    tot++;    h[tot].l=h[tot].r=h[tot].d=0;//开始左右孩子为0,距离为0     h[tot].v=p;    return tot;}int Merge(int a,int b)//合并{    if(a==0||b==0)return a+b;    if(h[a].v>h[b].v)swap(a,b);//我要维护一个小根堆,要让a作为父亲节点     int x=++tot;//新开一个点,因为要多次合并    h[x]=h[a];     h[x].r=Merge(h[a].r,b);//让我的右孩子和b合并,因为右孩子距离较小     if(h[h[x].l].d<h[h[x].r].d)swap(h[x].l,h[x].r);//维护左偏树,左孩子的距离要大一些     h[x].d=h[h[x].r].d+1;//肯定从右边插入距离较小     return x;}bool v[1100];void dfs(int y){    if(f[y]==0||v[y]==true)return ;//如果该点不在最短路径树或已经合并过     v[y]=true;    int x=a[f[y]].x;dfs(x);//让x先去往前面合并(实际上是后面,因为我们开始建的是反图)     root[y]=Merge(root[x],root[y]);//让最短路径树的这条边两端的点合并 }int main(){    int x,y,dd;    scanf("%d%d",&n,&m);    len=0;memset(last,0,sizeof(last));    for(int i=1;i<=m;i++)    {        scanf("%d%d%d",&x,&y,&dd);        ins(y,x,dd);    }    scanf("%d%d%d",&st,&ed,&K);    get_dis();    if(st==ed)K++;    if(d[st]>999999999){printf("-1\n");return 0;}    if(K==1){     printf("%d\n",d[st]);return 0;}    K--;    memset(b,false,sizeof(b));    memset(v,false,sizeof(v));    memset(root,0,sizeof(root));    for(int i=1;i<=n;i++)b[f[i]]=true;    h[0].d=-1;tot=0;    for(int i=1;i<=len;i++)        if(b[i]==false&&d[a[i].x]<999999999)//将不在最短路径树的边左右两点合并,如果无法到达也就没有必要了         {            int x=a[i].x,y=a[i].y;             root[y]=Merge(root[y],bt( P(a[i].d-(d[y]-d[x]),x) ));        }    for(int i=1;i<=n;i++)dfs(i);//再看看还有没有点可以合并     priority_queue < P,vector<P>,greater<P> > q;    if(root[st]!=0)q.push(P(d[st]+h[root[st]].v.first,root[st]));//最短路+多花的距离就是解     int ans;    while(q.empty()==false&&K>0)    {        K--;        P t=q.top();q.pop();        ans=t.first;x=t.second;        y=h[x].l;        if(y!=0)q.push(P(ans-h[x].v.first+h[y].v.first,y));        y=h[x].r;        if(y!=0)q.push(P(ans-h[x].v.first+h[y].v.first,y));        y=root[h[x].v.second];        if(y!=0)q.push(P(ans+h[y].v.first,y));    }    if(K>0)printf("-1\n");    else printf("%d\n",ans);    return 0;}
原创粉丝点击