6362. 【NOIP2019模拟2019.9.18】数星星
题目
题目大意
给你一棵带点权的树和许多条路径。
然后有一堆询问,每次询问一段区间内所有路径的并的点权和。
思考历程
比赛时看到这题,已经没有什么时间了。
果断地打了个树链剖分加莫队,信心满满地觉得自己能过。
维护的时候类似于扫描线。
才打了20分钟。
在比赛的最后一刻,我突然意识到这个做法的时间复杂度非常大。
甚至不如暴力。
于是TLE了……
正解
其实题目有个比较显而易见的方法(然而我居然没有想到)。按照询问右端点排个序,在所有路径中从左到右扫。对于每个点,记录最后被覆盖的时间。
在树状数组维护每条边覆盖的点,并且这些点最后一次是被这条边覆盖的,满足这些条件的点的权值和。(也就是说,每个点向最后被覆盖的时间的那个位置,贡献它的点权)
现在考虑如何维护这个东西。
题解中有个比较奇妙的做法:
假设这棵树是一条链(如果不是一条链就树链剖分)。
现在每次要覆盖一个区间。
其实这就是一个区间染色的问题。每次覆盖的区间所有点的颜色必然就会被修改。
于是就可以用传说中的珂朵莉树……
其实题解也没有说是这个数据结构,不过实际上就是。
珂朵莉树的核心思想是,将相同状态的点合并成区间,一同处理。
至于具体实现,常规是用set
,或者如果你喜欢也可以打平衡树或线段树之类的。每个节点维护的是一个相同状态的点组成的一个区间。
在平常的题目中,珂朵莉树要满是数据随机才可以达到\(O(n \lg n)\)的时间复杂度。但这题不一样。
在这题中,每个颜色相同的区间合在一起处理。在染色的时候,就要将这个区间覆盖的所有区间连同它们的贡献删去,再加入这个区间(相交的区间就分裂一下)。
显然,每次加入的区间新的区间个数是一个;并且它覆盖原来的区间之后,原来的那些区间都要被暴力删除,而它们只会被暴力删除一次。
所以这题的时间复杂度是有保障的,就是\(O(n \lg n)\)。
树链剖分之后时间复杂度要乘个\(\lg n\)。
或者还有一种非常简单的而且常数很小的方法:
直接上\(LCT\)。\(LCT\)上的每条链表示的是颜色相同的点(不过颜色相同的点不一定在同一条链里面),于是就可以将整条链的贡献在树状数组里面加加减减。
在修改的时候,求出\(LCA\)(不要用\(LCT\)直接求,因为在这里不是可以随便使用access
的)。然后将\(LCA\)和它父亲的那条边断掉。
接着access(u)
和access(v)
。在\(access\)的过程中,同时在树状数组上进行修改。
时间复杂度也是\(O(n \lg^2 n)\),常数极小,代码简短。
提醒一下:这题是不能mroot
的,因为mroot
需要用到access
,而access
是不能随便用的。
还有一种强大的分治做法,不过常数似乎很大。
设当前的大区间为\([L,R]\)。一个询问\([l,r]\)可以分成:\([l,mid]\)和\([mid+1,r]\)两部分。
首先将\([L,R]\)中的所有点建立一棵虚树。时间复杂度是\(O(n \lg n)\)的。
然后先处理\([mid+1,r]\)的路径。显然我们只需要记录每个节点它最早出现的时间,因为询问区间是从中点向两边延伸的。
于是就用传统的并查集染色做法扫过去,并且用树状数组维护一下每一条路径能覆盖到了的所有新点的和。
右边处理完之后就处理左边。先将并查集清空,同样地用并查集染色的做法。不过这次染到的新点就要在树状数组中减去它的贡献,同时用一个变量来存它的贡献。
因为指针一直都往左边移,在左边出现过的就需要在右边加上了。
在这时候同时处理询问,就是用右边树状数组存下的值加上左边这个变量存下的值。
时间复杂度也是\(O(n \lg^2 n)\)
代码
\(LCT\)做法
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define N 100010
#define ll long long
int n,m,Q;
int a[N];
ll t[N];
#define lowbit(x) ((x)&-(x))
inline void add(int x,ll c){
if (x<=0)
return;
for (;x<=m;x+=lowbit(x))
t[x]+=c;
}
inline ll query(int x){
ll res=0;
for (;x;x-=lowbit(x))
res+=t[x];
return res;
}
struct Node *null;
struct Node{
Node *fa,*c[2];
int col;
int val;
ll sum;
inline void upd(){sum=c[0]->sum+c[1]->sum+val;}
inline bool getson(){return fa->c[0]!=this;}
inline void rotate(){
Node *y=fa,*z=y->fa;
if (y->col)
col=y->col,y->col=0;
else
z->c[y->getson()]=this;
int k=getson();
fa=z;
y->c[k]=c[k^1],c[k^1]->fa=y;
c[k^1]=y,y->fa=this;
sum=y->sum,y->upd();
}
void splay(){
for (;!col;rotate())
if (!fa->col)
getson()!=fa->getson()?rotate():fa->rotate();
}
} d[N];
inline void cover(Node *x,int c){
Node *y=null;
for (;x!=null;y=x,x=x->fa){
x->splay();
x->c[1]->col=x->col;
x->c[1]=y;
add(y->col,-y->sum),add(x->col,y->sum);
y->col=0;
x->upd();
}
add(y->col,-y->sum);
add(y->col=c,y->sum);
}
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
int fa[N][17],dep[N];
void init(int x){
dep[x]=dep[fa[x][0]]+1;
for (int i=1;1<<i<dep[x];++i)
fa[x][i]=fa[fa[x][i-1]][i-1];
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[x][0])
fa[ei->to][0]=x,init(ei->to);
}
inline int LCA(int u,int v){
if (dep[u]<dep[v])
swap(u,v);
for (int k=dep[u]-dep[v],i=0;k;k>>=1,++i)
if (k&1) u=fa[u][i];
if (u==v) return u;
for (int i=16;i>=0;--i)
if (fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
struct Path{
int u,v;
} p[N];
struct Oper{
int l,r,num;
} o[N];
inline bool cmpo(const Oper &x,const Oper &y){return x.r<y.r;}
ll ans[N];
int main(){
freopen("star.in","r",stdin);
freopen("star.out","w",stdout);
scanf("%d%d%d",&n,&m,&Q);
null=d;
*null={null,null,null,0,0,0};
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
e[ne]={v,last[u]};
last[u]=e+ne++;
e[ne]={u,last[v]};
last[v]=e+ne++;
}
init(1);
for (int i=1;i<=n;++i)
d[i]={&d[fa[i][0]],null,null,-1,a[i],a[i]};
for (int i=1;i<=m;++i)
scanf("%d%d",&p[i].u,&p[i].v);
for (int i=1;i<=Q;++i)
scanf("%d%d",&o[i].l,&o[i].r),o[i].num=i;
sort(o+1,o+Q+1,cmpo);
for (int i=1,j=1;i<=m;++i){
int u=p[i].u,v=p[i].v,lca=LCA(u,v);
Node *anc;
d[lca].splay();
if (d[lca].c[0]!=null){
anc=&d[fa[lca][0]];
anc->splay();
anc->c[1]=null;
anc->upd();
d[lca].col=anc->col;
}
else
anc=d[lca].fa;
d[lca].fa=null;
cover(&d[u],i),cover(&d[v],i);
d[lca].splay();
d[lca].fa=anc;
for (;j<=Q && o[j].r<=i;++j)
ans[o[j].num]=query(o[j].r)-query(o[j].l-1);
}
for (int i=1;i<=Q;++i)
printf("%lld\n",ans[i]);
return 0;
}
总结
\(LCT\)真是太好打了……
看到类似于染色的题目,不要忘了\(LCT\)啊……