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