很久之前发最近才看到被驳回了。
虽然已经退役很久了不过还是补一下吧。
原题地址
题目直接贴了。(这个东西应该是可以直接用的啊。)
【题目分析】
一看这个数据范围,标准的数据结构题,而且基本上就是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的算法了。
这道题花了我2h+才搞定,可以说我是很弱了。
注意一个城市可以经过多次。
【解题思路】
###算法1
暴力枚举所有长度不超过k的路线,复杂度
O
(
n
k
)
O(n^k)
O(nk),可以得到10pt
###算法2
考虑路线权值的增加对答案的影响,令
W
(
A
)
W(A)
W(A)表示一种路线A的权值。
我们发现,若u能走到v,则
W
(
u
)
<
W
(
v
)
W(u)<W(v)
W(u)<W(v)
所以我们可以用一个堆来维护这个路线的权值。一开始将所有长度为1的路线放进堆里,然后进行k次操作。每次取出堆顶序列
A
A
A,然后进行扩展,将
A
A
A连到的所有路线都塞回去堆里。
因为一个点最多连向n个点,所以该算法时间复杂度为
O
(
n
k
⋅
l
o
g
n
k
)
O(nk·lognk)
O(nk⋅lognk),可以得到20pt
###算法3
考虑优化算法2,我们发现,对于每个序列
A
A
A的扩展,实际上不需要塞n条路线,我们只需要塞最优的至多k条就行了(因为已经取出了一些最优的,所以会少一些)。但这样对时间复杂度影响不大,那么我们考虑如何每次只塞一条路线?
实际上,我们只需要将每个点的出边按权值大小排序,然后用左儿子右兄弟表示法构图即可。这样我们的堆里面只有(n+k)个点。
据说时间复杂度是
O
(
n
2
+
k
l
o
n
g
(
n
+
k
)
)
O(n^2+klong(n+k))
O(n2+klong(n+k)),如果强行排序的话。关于排序的较差的方法个人没有细想,直接想再用堆维护就好了。。。所以我们就有了:
###算法4
显然算法3中排序的时间复杂度决定了我们最后的时间复杂度。
怎么更快的地将每个点所连到的边权排序呢?
我们既然已经用了一个堆,不妨再用一个。
我们可以将每个点能走到的点按权值建一个小根堆, 优先队列中记录这些信息:(权值, 最后一个点, 指向一个堆的指针)。
弹出一个序列 ( W , x , H ) (W,x,H) (W,x,H)时, 只要将 ( W + w H . l . i d − w x , H . l . i d , H . l ) (W+w_{H.l.id}−w_x,H.l.id,H.l) (W+wH.l.id−wx,H.l.id,H.l)、 ( W + w H . r . i d − w x , H . r . i d , H . r ) (W+w_{H.r.id}−w_x,H.r.id,H.r) (W+wH.r.id−wx,H.r.id,H.r)和 ( W + w H e a p x . i d , H e a p x . i d , H e a p x ) (W+w_{Heap_x.id},Heap_x.id,Heap_x) (W+wHeapx.id,Heapx.id,Heapx)放入优先队列即可。
其中 H . i d H.id H.id表示堆 H H H中权值最小的点的编号, H e a p i Heap_i Heapi表示一个包含点i能走到的所有点的堆。
现在问题转化成,对每个
i
i
i求出
H
e
a
p
i
Heap_i
Heapi。
我们用可并堆(如左偏树)预处理第
i
i
i个点到根的路径上前
2
j
2^j
2j个点的堆, 然后每个点能走到的点可以拆成三条链,于是可以用倍增得到。
时间复杂度 O ( n l o g 2 n + k l o g ( n + k ) ) O(nlog^2n+klog(n+k)) O(nlog2n+klog(n+k)),空间复杂度 O ( n l o g 2 n + k ) O(nlog^2n+k) O(nlog2n+k)。 能得到60pt。
###算法5(我并没有想到)
考虑算法3,考虑算法三, 如果我们能在
O
(
f
(
n
)
)
O(f(n))
O(f(n))的时间内求得第
i
i
i个点能走到的点中,权值第
j
j
j大的点, 就能在
O
(
g
(
n
)
+
k
l
o
g
(
n
+
k
)
+
k
⋅
f
(
n
)
)
O(g(n)+klog(n+k)+k⋅f(n))
O(g(n)+klog(n+k)+k⋅f(n))的时间内解决原问题,其中
O
(
g
(
n
)
)
O(g(n))
O(g(n))的时间用来预处理。
算法三中 f ( n ) = O ( 1 ) f(n)=O(1) f(n)=O(1)), g ( n ) = O ( n 2 ) g(n)=O(n^2) g(n)=O(n2)。
但在这里,我们用主席树(可持久化线段树前缀和)来维护 1 ∼ i 1∼i 1∼i路径上点的权值,就可以使 f ( n ) = O ( l o g n ) f(n)=O(logn) f(n)=O(logn), g ( n ) = O ( n l o g n ) g(n)=O(nlogn) g(n)=O(nlogn)。
于是总的时间复杂度为 O ( n l o g n + k l o g ( n + k ) ) O(nlogn+klog(n+k)) O(nlogn+klog(n+k)),空间复杂度为 O ( n l o g n + k ) O(nlogn+k) O(nlogn+k)。
能通过1 ∼ 16号测试点。 对于 n = 5 × 1 0 5 n=5×10^5 n=5×105的数据会MLE。
###算法6
考虑对算法4进行优化,对于一条链
(
u
,
v
)
(u,v)
(u,v),将这条链上的点建成一个堆
H
H
H, 那么
H
.
i
d
H.id
H.id就是这条链上权值最小的点。 我们将这条链从这个点处断开, 那么剩下的两段分别对应于
H
.
l
H.l
H.l和
H
.
r
H.r
H.r。
我们用一个二元组 ( a , b ) (a,b) (a,b)表示 ( a , b ) (a,b) (a,b)这条链的堆, 就不用预处理出算法四中的 H e a p i Heapi Heapi了。
复杂度为 O ( n l o g n + k l o g ( n + k ) + k ⋅ f ( n ) ) O(nlogn+klog(n+k)+k⋅f(n)) O(nlogn+klog(n+k)+k⋅f(n)), 其中 f ( n ) f(n) f(n)为求出一条链上权值最小的点的时间复杂度。
用 倍增 or LCT or 树链剖分(要预处理每条重链的前缀minmin) 都可以做到 f ( n ) = O ( l o g n ) f(n)=O(logn) f(n)=O(logn)。
那么时间复杂度就是 O ( n l o g n + k l o g ( n + k ) ) O(nlogn+klog(n+k)) O(nlogn+klog(n+k)),跟算法五一样。
如果你用倍增,空间复杂度是 O ( n l o g n + k ) O(nlogn+k) O(nlogn+k),很有可能会MLE。
如果你用LCT,空间复杂度是 O ( n + k ) O(n+k) O(n+k)的,但是常数太大,很有可能会TLE。
用树链剖分就可以通过所有测试点了。
【总结】
做这道题给了我一个很重要的启发,其实挺多OI题的部分分是在为AC作铺垫,对于一个点一个个瓶颈的解决,可能正是通向AC的道路。可以说是一道毒瘤好题吧。
另外得到了一个重点:inline对于函数的加速并不是无代价的,它会通过内存加速,对于这题来说,我的100pt程序由于使用了inline,开始只有80pt,而我MLE了很久才想到这个可能性,这样才最终通过了这题。
####UPDATE忘记贴代码
【代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=2e9;
const int MAXN=5e5+5;
int n,m,topp,cnt,ans,tot;
int head[MAXN],d[MAXN],fa[MAXN],f[MAXN];
int siz[MAXN],son[MAXN],top[MAXN],vi[MAXN<<3],wi[MAXN];
int b[MAXN][5],c[MAXN][5],p[MAXN],fp[MAXN],ll[MAXN];
struct Tway
{
int nex,v;
};
Tway e[MAXN];
struct Tnode
{
int s,t,k,w;
};
Tnode q[MAXN*5];
bool operator <(const Tnode &x,const Tnode &y)
{
return x.w>y.w;
}
int calc(int x,int y)
{
return wi[x]<wi[y]?x:y;
}
void dfs(int x,int ff)
{
d[x]=d[ff]+1;siz[x]=1;fa[x]=ff;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==ff)
continue;
dfs(v,x);
siz[x]+=siz[v];
if(siz[v]>siz[son[x]])
son[x]=v;
}
}
void dfss(int x,int ff)
{
p[x]=++tot;fp[tot]=x;
if(son[ff]==x)
top[x]=top[ff];
else
top[x]=x;
if(son[x])
{
f[son[x]]=calc(f[x],son[x]);
dfss(son[x],x);
}
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==ff || v==son[x])
continue;
f[v]=v;
dfss(v,x);
}
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]])
swap(x,y);
x=fa[top[x]];
}
return d[x]<d[y]?x:y;
}
int lastt(int x,int y)
{
int tmp;
while(top[x]!=top[y])
{
tmp=top[y];
y=fa[tmp];
}
return x==y?tmp:son[x];
}
void build(int rt,int l,int r)
{
if(l==r)
{
vi[rt]=fp[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
vi[rt]=calc(vi[rt<<1],vi[rt<<1|1]);
}
void query(int rt,int l,int r,int L,int R)
{
if(L<=l && r<=R)
{
ans=calc(ans,vi[rt]);
return;
}
int mid=(l+r)>>1;
if(L<=mid)
query(rt<<1,l,mid,L,R);
if(R>mid)
query(rt<<1|1,mid+1,r,L,R);
/* if(L==l && r==R)
{
ans=calc(ans,vi[rt]);
return;
}
int mid=(l+r)>>1;
if(R<=mid)
query(rt<<1,l,mid,L,R);
else
if(L>mid)
query(rt<<1|1,mid+1,r,L,R);
else
query(rt<<1,l,mid,L,mid),query(rt<<1|1,mid+1,r,mid+1,R);*/
}
int getp(int x,int y)
{
ans=0;
while(top[x]!=top[y])
{
ans=calc(ans,f[y]);
y=fa[top[y]];
}
// printf("%d %d %d %d..\n",x,y,p[x],p[y]);
query(1,1,n,p[x],p[y]);
return ans;
}
void add(int u,int v)
{
++cnt;
e[cnt].v=v;e[cnt].nex=head[u];
head[u]=cnt;
}
void gdd(int x,int y,int z)
{
b[x][++ll[x]]=y;
c[x][ll[x]]=z;
}
void init()
{
scanf("%d%d",&n,&m);
wi[0]=INF;
for(int i=1;i<=n;++i)
scanf("%d",&wi[i]);
for(int i=2;i<=n;++i)
{
int x;
scanf("%d",&x);
add(x,i);
}
dfs(1,0);dfss(1,0);
build(1,1,n);
//for(int i=1;i<=n;++i)
//printf("%d %d %d\n",son[i],top[i],f[i]);
for(int i=1;i<=n;++i)
{
int x,y,z,xx,yy;
scanf("%d%d%d",&x,&y,&z);
xx=LCA(x,y);yy=LCA(y,z);
// printf("%d %d\n",xx,yy);
if(d[xx]>d[yy])
swap(xx,yy),swap(x,z);
else
if(d[xx]==d[yy])
swap(x,y),yy=LCA(y,z);
q[++topp]=(Tnode){i,i,i,wi[i]};
push_heap(q+1,q+topp+1);
gdd(i,xx,x);
if(xx!=z)
gdd(i,lastt(xx,z),z);
if(yy!=y)
gdd(i,lastt(yy,y),y);
}
/* while(topp!=0)
{
Tnode tp=q[1];pop_heap(q+1,q+topp+1);topp--;
printf("%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
}*/
}
void solve()
{
while(m--)
{
int x,y;
Tnode tp=q[1];pop_heap(q+1,q+topp+1);topp--;
printf("%d\n",tp.w);
// printf("%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
if(tp.s!=tp.k)
{
x=getp(tp.s,fa[tp.k]);
q[++topp]=(Tnode){tp.s,fa[tp.k],x,tp.w-wi[tp.k]+wi[x]};
push_heap(q+1,q+topp+1);
// printf("@%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
// printf("x:%d\n",x);
}
if(tp.t!=tp.k)
{
x=lastt(tp.k,tp.t);
y=getp(x,tp.t);
q[++topp]=(Tnode){x,tp.t,y,tp.w-wi[tp.k]+wi[y]};
push_heap(q+1,q+topp+1);
// printf("@%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
// printf("x:%d y:%d\n",x,y);
}
// printf("ll:%d\n",ll[tp.k]);
for(int i=1;i<=ll[tp.k];++i)
{
x=getp(b[tp.k][i],c[tp.k][i]);
q[++topp]=(Tnode){b[tp.k][i],c[tp.k][i],x,tp.w+wi[x]};
push_heap(q+1,q+topp+1);
// printf("@%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
// printf("!%d %d %d\n",x,b[tp.k][i],c[tp.k][i]);
}
}
}
int main()
{
// freopen("UOJ53.in","r",stdin);
// freopen("UOJ53.out","w",stdout);
init();
solve();
return 0;
}