第二次被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代碼