题意:
给你一个n个点m条边的无向图,边有边权,点有点权。有q次询问,每次问你从x出发,只能经过边权不超过y的边,能到达的所有点中点权第k大的权值是多少,如果不存在就输出-1。n<=1e5,m,q<=5e5
BZOJ3551和另外两个题号的题面的区别是多了一个强制在线。
题解:
先吐槽两句。写这个题真的是心态崩了。这个题本来并不怎么难,但是我却一直RE,过了对拍也没有用。在洛谷上提交了一页多的RE,又借同学的BZOJ权限号在BZOJ上交,还是RE。最后终于在一些奇奇怪怪的地方下了数据,然后发现,m是5e5???我一直看成了n,m<=1e5,q<=5e5,于是成功地一直RE。
下面说一下做法。
我写了两个写法,都是在线的。
我们先对边权求出一个Kruskal重构树,然后我们对于每次询问,还是在树上倍增找到最靠近根的合法的点,然后以这个点为根的子树内的所有点都是能到达的。那么我们就要找子树内的这些点权值第k大的了。由于点的权值是1e9级别的,所以我们先离散化一下。
这时候我有两种做法,第一个是用线段树合并,具体就是每次加进一个叶子就把它对应的离散化后的权值的位置+1,然后查询的话就找到这个点的这棵线段树,然后类似主席树找第k大的样子,判断左右子树的大小来决定递归到哪边。然后还要注意一下特判-1的情况。
另一个做法是用主席树维护dfs序。我们先处理出每个点在Kruskal重构树上的dfs序及子树结束时的dfs序。我们按照dfs序加入每一个点对应的权值,然后一个子树内的所有点对应的就是dfs序上一个区间,由于主席树是可以前缀相减的,于是减一下就能得到这个区间对应的主席树。于是剩下的就是主席树上找第k大了。
两种代码都放一下。
主席树的写法:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;
int n,m,q,bb[400010],c[400010],hed[400010],cnt,hed1[400010],cnt1;
int fa[400010],f[400010][21],xu[400010],num,num1,val[400010];
int ji[400010],ed[400010],root[400010];
struct node
{
int from,to,next,c;
}a[400010],b[800010];
struct tree
{
int l,r,s;
}tr[4000010];
inline int read()
{
int x=0,f=1;
char s=getchar();
while(s>'9'||s<'0')
{
if(s=='-')
f=-1;
s=getchar();
}
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x*f;
}
inline void add(int from,int to,int c)
{
a[++cnt].to=to;
a[cnt].from=from;
a[cnt].next=hed[from];
a[cnt].c=c;
hed[from]=cnt;
}
inline int cmp(node x,node y)
{
return x.c<y.c;
}
inline int getr(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=getr(fa[x]);
return fa[x];
}
}
inline void add1(int from,int to)
{
b[++cnt1].to=to;
b[cnt1].next=hed1[from];
hed1[from]=cnt1;
}
inline void dfs(int x)
{
xu[x]=++cnt;
ji[cnt]=x;
for(int i=1;i<=20;++i)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=hed1[x];i;i=b[i].next)
{
int y=b[i].to;
if(y==f[x][0])
continue;
f[y][0]=x;
dfs(y);
}
ed[x]=cnt;
}
inline void update(int rt,int rt2,int l,int r,int x)
{
tr[rt].s=tr[rt2].s;
tr[rt].s++;
if(l==r)
return;
int mid=(l+r)>>1;
if(x<=mid)
{
tr[rt].l=++cnt;
tr[rt].r=tr[rt2].r;
update(tr[rt].l,tr[rt2].l,l,mid,x);
}
else
{
tr[rt].r=++cnt;
tr[rt].l=tr[rt2].l;
update(tr[rt].r,tr[rt2].r,mid+1,r,x);
}
}
inline int lca(int x,int y)
{
for(int i=20;i>=0;--i)
{
if(val[f[x][i]]<=y)
x=f[x][i];
}
return x;
}
inline int query(int rt1,int rt2,int l,int r,int k)
{
if(l==r&&l==1&&k>tr[rt1].s-tr[rt2].s)
return -1;
if(l==r)
return l;
int s=tr[tr[rt1].r].s-tr[tr[rt2].r].s,mid=(l+r)>>1;
if(s<k)
return query(tr[rt1].l,tr[rt2].l,l,mid,k-s);
else
return query(tr[rt1].r,tr[rt2].r,mid+1,r,k);
}
int main()
{
n=read();
m=read();
q=read();
for(int i=1;i<=n;++i)
{
c[i]=read();
bb[i]=c[i];
}
for(int i=1;i<=m;++i)
{
int x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
sort(bb+1,bb+n+1);
num=unique(bb+1,bb+n+1)-bb-1;
for(int i=1;i<=n;++i)
c[i]=lower_bound(bb+1,bb+num+1,c[i])-bb;
sort(a+1,a+cnt+1,cmp);
for(int i=1;i<=2*n;++i)
fa[i]=i;
for(int i=1;i<=n;++i)
val[i]=0;
num1=n;
for(int i=1;i<=cnt;++i)
{
int fx=getr(a[i].from),fy=getr(a[i].to);
if(fx!=fy)
{
++num1;
add1(fx,num1);
add1(num1,fx);
add1(num1,fy);
add1(fy,num1);
val[num1]=a[i].c;
fa[fx]=fa[fy]=num1;
}
}
val[0]=2e9;
cnt=0;
dfs(num1);
cnt=0;
root[0]=++cnt;
for(int i=1;i<=num1;++i)
{
root[i]=++cnt;
if(c[ji[i]]!=0)
update(root[i],root[i-1],1,2*n,c[ji[i]]);
else
root[i]=root[i-1];
}
for(int i=1;i<=q;++i)
{
int x=read(),y=read(),z,k=read();
z=lca(x,y);
int gg;
gg=query(root[ed[z]],root[xu[z]-1],1,2*n,k);
if(gg==-1)
printf("-1\n");
else
printf("%d\n",bb[gg]);
}
return 0;
}
线段树合并写法:
#include <bits/stdc++.h>
using namespace std;
int n,m,q,hed[200010],cnt,hed1[200010],cnt1;
int bb[200010],c[200010],num,fa[200010],f[200010][21],num1;
int root[200010],val[200010];
struct node
{
int from,to,l,next;
}a[1000010],b[2000010];
struct tree
{
int l,r,s;
}tr[6000010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to,int l)
{
a[++cnt].from=from;
a[cnt].to=to;
a[cnt].l=l;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline int cmp(node x,node y)
{
return x.l<y.l;
}
inline int getr(int x)
{
if(x==fa[x])
return fa[x];
else
{
fa[x]=getr(fa[x]);
return fa[x];
}
}
inline void add1(int from,int to)
{
b[++cnt1].to=to;
b[cnt1].next=hed1[from];
hed1[from]=cnt1;
}
inline void update(int &rt,int l,int r,int x)
{
if(!rt)
rt=++cnt;
tr[rt].s++;
if(l==r)
return;
int mid=(l+r)>>1;
if(x<=mid)
update(tr[rt].l,l,mid,x);
else
update(tr[rt].r,mid+1,r,x);
}
inline void merge(int &rt,int l,int r)
{
if(!l||!r)
{
rt=l+r;
return;
}
rt=++cnt;
tr[rt].s=tr[l].s+tr[r].s;
merge(tr[rt].l,tr[l].l,tr[r].l);
merge(tr[rt].r,tr[l].r,tr[r].r);
}
inline void dfs(int x)
{
for(int i=1;i<=20;++i)
f[x][i]=f[f[x][i-1]][i-1];
int pd=0;
for(int i=hed1[x];i;i=b[i].next)
{
int y=b[i].to;
if(y==f[x][0])
continue;
pd=1;
f[y][0]=x;
dfs(y);
merge(root[x],root[x],root[y]);
}
if(pd==0)
update(root[x],1,n,c[x]);
}
inline int lca(int x,int y)
{
for(int i=20;i>=0;--i)
{
if(val[f[x][i]]<=y)
x=f[x][i];
}
return x;
}
inline int query(int rt,int l,int r,int k)
{
if(l==r&&tr[rt].s<k)
return -1;
if(l==r)
return bb[l];
int mid=(l+r)>>1;
if(tr[tr[rt].r].s>=k)
return query(tr[rt].r,mid+1,r,k);
else
return query(tr[rt].l,l,mid,k-tr[tr[rt].r].s);
}
int main()
{
n=read();
m=read();
q=read();
for(int i=1;i<=n;++i)
{
c[i]=read();
bb[i]=c[i];
}
sort(bb+1,bb+n+1);
num=unique(bb+1,bb+n+1)-bb-1;
for(int i=1;i<=n;++i)
c[i]=lower_bound(bb+1,bb+num+1,c[i])-bb;
for(int i=1;i<=m;++i)
{
int x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
sort(a+1,a+cnt+1,cmp);
for(int i=1;i<=2*n;++i)
fa[i]=i;
num1=n;
for(int i=1;i<=cnt;++i)
{
int fx=getr(a[i].from),fy=getr(a[i].to);
if(fx!=fy)
{
++num1;
add1(num1,fx);
add1(fx,num1);
add1(num1,fy);
add1(fy,num1);
val[num1]=a[i].l;
fa[fx]=fa[fy]=num1;
}
}
val[0]=2e9;
cnt=0;
dfs(num1);
for(int i=1;i<=q;++i)
{
int x=read(),y=read(),k=read(),z;
z=lca(x,y);
int ji=query(root[z],1,n,k);
printf("%d\n",ji);
}
return 0;
}


498

被折叠的 条评论
为什么被折叠?



