线段树优化连边,Dijkstra(In Touch,HDU 5361)

第二次被vector卡空间。。。但凡用到了vector,空间复杂度就别想算对了。。。以后少用vector,多用链式前向星。(特别是空间比较紧的时候)


还有一些实现上的失误,

a=max(1,i-R[i]);
b=max(1,i-L[i]);
改成

a=i-R[i];
b=i-L[i];
就对了,因为前面的代码会导致不一样的结果。

对于这种问题,我只能说对于那些比较关键的代码,应该仔细思考这样写会不会导致一些意料之外的后果。(这是属于实现的问题)

可能一开始没仔细想,就随便实现了,然后到后面就会自然地认为这么写是对的,在一开始写代码的时候就应该写对它,检查的时候也应该关注这方面的问题。


其实自己线段树优化连边好像一开始就没学会,简直乱搞。

应该是父节点向两个儿子节点各连一条距离为0的边,所有叶子节点向对应节点连一条距离为0的边就好了,而不是直接区间内所有点都暴力连。。。

参考博客:http://blog.csdn.net/gengmingrui/article/details/50729968


看了下网上的做法,大多数是改造Dijkstra。

正常的Dijkstra是每次找一个还未使用过的距离最小的节点,然后用它去更新所有和它相邻的节点。

算法保证使用过的节点距离一定最优了,如果一个节点还没被使用而且距离最小,那么这个节点一定也是一个距离最优的节点,我们就可以放心地用它去更新其他节点,然后将之抛弃了。任何时刻可以用于更新其他节点的节点只有一个,那就是距离最小的节点,因为Dijkstra只适用于非负的图,因此距离最小的节点不可能再被别人更新,所以才可以去更新别人。同时这个节点也算是物尽其用了,所以我们可以将之抛弃。我们必须按照某种顺序使用点和边来实现算法,而边又依附于点,因此按照点距离大小的顺序是很好的选择。


当然,最短路算法的思想有很多,具体实现也有很多,Dijkstra只是一种按拓扑序动态规划的思想,具体实现也只是针对最一般的情况。事实上如果情况不那么一般,Dijkstra算法的具体实现当然也可以有针对性地千变万化。

比如在本题中,图是线性的,关系是连续的,点与点之间的关系不再那么拓扑,而常规的Dijkstra又无法在规定的时间内得到答案,我们就可以尝试用其思想创造一个算法。

思想还是一样的,拓扑序的动态规划,其实动态规划方面基本上不会有什么变化,状态定义无非就是和距离相关,状态转移无非就是用一个点的所有边去更新其他附属的节点。

在本题中,每个点有且只有一个传送门,这个传送门可以往两边传送一段特定范围的距离,而且无论距离多远,花费固定。

常规算法之所以无法解决,是因为点不少,而且边太多。但是点的排布(在一条线上)和边的排布(一段连续的区间)实在太有规律了,因此对算法进行改造的可能性和必要性都是很足的。一般来讲这类单点连到区间的问题都可以用线段树优化连边来解决,本题也是可以这样解决的,只不过时间会久一些,空间比较吃紧。

1<=n<=2e5,那么边的个数最多可以是O(n^2)级别的,光是枚举一遍就超时了,而且这种数据题目一定是会给的(太容易出了)。

我们必须要把重点放在点上,尝试用枚举点的时间复杂度来完成这个问题。

这就要求我们每个点只能枚举常数次。点刚好是连成一条线的,边也是连续的,如果可以把枚举后的点都缩掉或删掉,使得枚举边的时候能够跳过那些已经枚举过的点,时间复杂度就满足了。

但是主要问题有两个。

1、如何缩掉或删掉。

2、如何保证枚举一次后就可以抛弃(要求最优)。

还是按照拓扑序的动态规划,但是顺序有所不同。

我们这次要求更新的顺序是,更新后保证最优,然后就可以将其抛弃。而不是常规算法的要求了。

显然我们直接用优先队列去维护更新后保证最优的节点就好了。(就是更新后距离最小的节点,因为边非负,所以除此之外没有别人能更新它了)但是这样做的后果是很糟糕的,因为优先队列中的节点个数最差会有O(n^2)个,时间复杂度达到O(n^2logn),无法接受。

所幸的是一个传送门的出口虽然有很多,但是入口只有一个,一个入口也只有一个传送门,而且花费固定。我们就可以用入口来代表所有出口。

这样第2个问题就解决了。


关于第1个问题的话,如果删掉,很不现实,因为删除就涉及了很多合并,维护的操作,(点,边的数据都要大量修改)这本身也是需要时间复杂度的,而且不好实现。

因此缩掉或略过是一个好主意。

如果一个点已经被抛弃,我们就将其标记,并且记录其右边(或左边)第一个没被抛弃的节点。

这会涉及大量点或集合的合并,并查集是一个好的解决办法。

参考博客:

http://blog.csdn.net/u013007900/article/details/47338385

http://www.cnblogs.com/oneshot/p/4709207.html


看了别人的代码,以为自己懂了,然后WA了一下午不知所以。

我只能说,以后最好还是要看懂别人的文字说明以后,再去看代码什么的,否则你可能只是以为自己懂了,然后浪费很多时间。


线段树优化连边代码

#include<stdio.h>
#include<limits.h>
#include<queue>
#define ls (now<<1)
#define rs (ls|1)
#define m ((l+r)>>1)
using namespace std;
typedef long long ll;
const int maxn = 200010;
const int maxm = maxn*50;
const ll inf = LONG_LONG_MAX>>2;
struct Edge
{
    int v,n,w;
}edges[maxm];
int head[maxn<<3],tot;
void Init(int n)
{
    tot=0;
    for(int i=0;i<=n;i++) head[i]=-1;
}
void add(int u,int v,int w)
{
    edges[tot].v=v;
    edges[tot].w=w;
    edges[tot].n=head[u];
    head[u]=tot++;
}

struct HeapNode
{
    ll d;
    int u;
    bool operator < (const HeapNode& rhs) const
    {
        return d>rhs.d;
    }
};

struct Dijkstra
{
    int n;
    ll d[maxn<<3];
    bool done[maxn<<3];

    void init(int n)
    {
        this->n=n;
        Init(n);
    }

    void dijkstra(int s)
    {
        for(int i=0;i<=n;i++)
        {
            done[i]=0;
            d[i]=inf;
        }
        d[s]=0;
        priority_queue<HeapNode>Q;
        Q.push((HeapNode){0,s});
        while(!Q.empty())
        {
            HeapNode now = Q.top();
            Q.pop();
            int u = now.u;
            if(done[u]) continue;
            done[u]=true;
            for(int e = head[u];~e;e=edges[e].n)
            {
                int v = edges[e].v;
                if(d[v]>d[u]+edges[e].w)
                {
                    d[v]=d[u]+edges[e].w;
                    Q.push((HeapNode){d[v],v});
                }
            }
        }
    }
}DIJ;

int n;
int L[maxn],R[maxn],C[maxn];

void build(int l,int r,int now)
{
    if(l==r)
    {
        add(now+n,l,0);
        return;
    }
    add(now+n,ls+n,0);
    add(now+n,rs+n,0);
    build(l,m,ls);
    build(m+1,r,rs);
}

void qry(int l,int r,int now,int pos,int ql,int qr)
{
    if(r<ql||l>qr) return;
    if(ql<=l&&r<=qr)
    {
        add(pos,now+n,C[pos]);
        return;
    }
    qry(l,m,ls,pos,ql,qr);
    qry(m+1,r,rs,pos,ql,qr);
}

void solve()
{
    scanf("%d",&n);
    DIJ.init(n<<3);
    build(1,n,1);
    for(int i=1;i<=n;i++) scanf("%d",L+i);
    for(int i=1;i<=n;i++) scanf("%d",R+i);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",C+i);
        int a,b;
        a=i-R[i];
        b=i-L[i];
        qry(1,n,1,i,a,b);
        a=i+L[i];
        b=i+R[i];
        qry(1,n,1,i,a,b);
    }
    DIJ.dijkstra(1);
    for(int i=1;i<=n;i++) printf("%lld%c",DIJ.d[i]==inf?-1:DIJ.d[i],i==n?'\n':' ');
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}

改造Dijkstra代码


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章