题解:P17855 [LBA-OI R6 C] 哥德巴赫猜想
赛时想的是主席树的做法,被卡空间了,然后就撞不过去了。
这里用的是自认为比较简单的树剖和线段树做法,时空都是单
以下称
从
线段树处理出每个区间的最大值,然后每次跳链,将每条链加入一个全局大根堆。删点时,就将原来的链一分二,重新加入全局堆。
每次会加入
单次操作一是
我们想如果每次只加入一条链,每次查询
每次只加入一条链还是很好实现的,只要记录好端点即可,那么每次查询怎么实现成单
观察每次跳链,从左区间到右区间的过程,只有一次线段树查询不是查当前点到链头。这一次因为左区间可以是链上的不同点,所以这一次不可省。
那么直接跳到链头的呢,我们可以在 dfs 时直接处理出来,每个点到所在链头的路径上最大值节点编号。
这样查询时每次跳到链头就可以
操作二就直接断链,由于我们的链认父亲,下半区间不一定是哪一条链上的儿子,为了下半区间方便,考虑转移时,直接写成左开右闭的区间。这里不懂的看看代码。
由于我们每次都是跳链的操作,所以操作二转移时,不需要考虑任何 dfn 序,因为左区间从一开始一直保持是右区间的祖先,所以最后跳链一定会相遇。
::::info[代码]
#include<bits/stdc++.h>
using namespace std;
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int rd()
{
int x=0,f=1;
char ch=nc();
while(ch<48||ch>57)
{
if(ch=='-')
f=-1;
ch=nc();
}
while(ch>=48&&ch<=57)
x=x*10+ch-48,ch=nc();
return x*f;
}
int fat[1000005],size[1000005],dfn[1000005],dfncnt=0,mapp[1000005];
int premax[1000005];
int maxn[4000005],maxid[4000005],son[1000005],w[1000005];
int head[1000005],ecnt=0;
struct
{
int v,next;
}edge[1000005];
void dfs(int u)
{
size[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].v;
dfs(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
int top[1000005];
void dfs2(int u,int root)
{
dfn[u]=++dfncnt;
mapp[dfncnt]=u;
if(top[fat[u]]==root)
{
if(w[premax[fat[u]]]<w[u]) premax[u]=u;
else premax[u]=premax[fat[u]];
}
else premax[u]=u;
top[u]=root;
if(son[u]) dfs2(son[u],root);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].v;
if(v!=son[u]) dfs2(v,v);
}
}
struct aa
{
int l,r,id;
};
struct cmp
{
bool operator()(const aa& x,const aa& y)
{
return w[x.id]<w[y.id];
}
};
priority_queue<aa,vector<aa>,cmp> q;
void build(int cnt,int l,int r)
{
if(l==r){maxn[cnt]=w[mapp[l]];maxid[cnt]=l;return ;}
int mid=(r+l)>>1;
build(cnt<<1,l,mid);build(cnt<<1|1,mid+1,r);
if(maxn[cnt<<1]>maxn[cnt<<1|1]){maxn[cnt]=maxn[cnt<<1];maxid[cnt]=maxid[cnt<<1];}
else{maxn[cnt]=maxn[cnt<<1|1];maxid[cnt]=maxid[cnt<<1|1];}
}
pair<int,int> query(int cnt,int l,int r,int jobl,int jobr)
{
if(jobl<=l&&jobr>=r) return {maxn[cnt],maxid[cnt]};
int mid=(r+l)>>1;
int ans=0,k=0;
if(jobl<=mid)
{
auto p=query(cnt<<1,l,mid,jobl,jobr);
ans=p.first;k=p.second;
}
if(jobr>mid)
{
auto p=query(cnt<<1|1,mid+1,r,jobl,jobr);
if(p.first>ans)
{
ans=p.first;k=p.second;
}
}
return {ans,k};
}
int n,m;
void path_query(int l,int r)
{
int k=0;int z=r;
while(top[l]!=top[r])
{
if(w[premax[r]]>w[k]) k=premax[r];
r=fat[top[r]];
}
if(r!=l)
{
auto p=query(1,1,n,dfn[l]+1,dfn[r]);//左开区间
if(p.first>w[k]) k=mapp[p.second];
}
if(k!=0) q.push((aa){l,z,k});
}
void add(int u,int v)
{
edge[++ecnt].v=v;
edge[ecnt].next=head[u];
head[u]=ecnt;
}
int main()
{
n=rd();m=rd();
for(int i=1;i<=n;i++)
{
fat[i]=rd();
if(fat[i]!=0) add(fat[i],i);
}
for(int i=1;i<=n;i++) w[i]=rd();
for(int i=1;i<=n;i++)
{
if(fat[i]==0)
{
dfs(i);
premax[i]=i;dfs2(i,i);
}
}
build(1,1,n);
long long last=0;int tot=0;
for(int i=1;i<=m;i++)
{
int opt;opt=rd();
if(opt==1)
{
int x;x=rd();
path_query(0,x);//左边开区间到0
}
else
{
tot++;
if(q.empty()) continue;
aa k=q.top();q.pop();
last^=(1ll*w[k.id]*tot);
if(k.l!=k.id) path_query(k.l,fat[k.id]);
if(k.r!=k.id) path_query(k.id,k.r);//左边开区间,直接用k.id即可。
}
}
printf("%lld",last);
}
::::
警示后人,不要用 vector 存边,空间巨大,对于树形结构边不多,用链式前向星飞快。