線段樹優化連邊,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代碼


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