6362. 【NOIP2019模拟2019.9.18】数星星

10 篇文章 0 订阅
1 篇文章 0 订阅

题目

题目大意

给你一棵带点权的树和许多条路径。
然后有一堆询问,每次询问一段区间内所有路径的并的点权和。


思考历程

比赛时看到这题,已经没有什么时间了。
果断地打了个树链剖分加莫队,信心满满地觉得自己能过。
维护的时候类似于扫描线。
才打了20分钟。
在比赛的最后一刻,我突然意识到这个做法的时间复杂度非常大。
甚至不如暴力。
于是TLE了……


正解

其实题目有个比较显而易见的方法(然而我居然没有想到)。按照询问右端点排个序,在所有路径中从左到右扫。对于每个点,记录最后被覆盖的时间。
在树状数组维护每条边覆盖的点,并且这些点最后一次是被这条边覆盖的,满足这些条件的点的权值和。(也就是说,每个点向最后被覆盖的时间的那个位置,贡献它的点权)

现在考虑如何维护这个东西。

题解中有个比较奇妙的做法:
假设这棵树是一条链(如果不是一条链就树链剖分)。
现在每次要覆盖一个区间。
其实这就是一个区间染色的问题。每次覆盖的区间所有点的颜色必然就会被修改。
于是就可以用传说中的珂朵莉树……
其实题解也没有说是这个数据结构,不过实际上就是。
珂朵莉树的核心思想是,将相同状态的点合并成区间,一同处理。
至于具体实现,常规是用set,或者如果你喜欢也可以打平衡树或线段树之类的。每个节点维护的是一个相同状态的点组成的一个区间。
在平常的题目中,珂朵莉树要满是数据随机才可以达到 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)的时间复杂度。但这题不一样。
在这题中,每个颜色相同的区间合在一起处理。在染色的时候,就要将这个区间覆盖的所有区间连同它们的贡献删去,再加入这个区间(相交的区间就分裂一下)。
显然,每次加入的区间新的区间个数是一个;并且它覆盖原来的区间之后,原来的那些区间都要被暴力删除,而它们只会被暴力删除一次。
所以这题的时间复杂度是有保障的,就是 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)
树链剖分之后时间复杂度要乘个 lg ⁡ n \lg n lgn

或者还有一种非常简单的而且常数很小的方法:
直接上 L C T LCT LCT L C T LCT LCT上的每条链表示的是颜色相同的点(不过颜色相同的点不一定在同一条链里面),于是就可以将整条链的贡献在树状数组里面加加减减。
在修改的时候,求出 L C A LCA LCA(不要用 L C T LCT LCT直接求,因为在这里不是可以随便使用access的)。然后将 L C A LCA LCA和它父亲的那条边断掉。
接着access(u)access(v)。在 a c c e s s access access的过程中,同时在树状数组上进行修改。
时间复杂度也是 O ( n lg ⁡ 2 n ) O(n \lg^2 n) O(nlg2n),常数极小,代码简短。
提醒一下:这题是不能mroot的,因为mroot需要用到access,而access是不能随便用的。

还有一种强大的分治做法,不过常数似乎很大。
设当前的大区间为 [ L , R ] [L,R] [L,R]。一个询问 [ l , r ] [l,r] [l,r]可以分成: [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r]两部分。
首先将 [ L , R ] [L,R] [L,R]中的所有点建立一棵虚树。时间复杂度是 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)的。
然后先处理 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]的路径。显然我们只需要记录每个节点它最早出现的时间,因为询问区间是从中点向两边延伸的。
于是就用传统的并查集染色做法扫过去,并且用树状数组维护一下每一条路径能覆盖到了的所有新点的和。
右边处理完之后就处理左边。先将并查集清空,同样地用并查集染色的做法。不过这次染到的新点就要在树状数组中减去它的贡献,同时用一个变量来存它的贡献。
因为指针一直都往左边移,在左边出现过的就需要在右边加上了。
在这时候同时处理询问,就是用右边树状数组存下的值加上左边这个变量存下的值。
时间复杂度也是 O ( n lg ⁡ 2 n ) O(n \lg^2 n) O(nlg2n)


代码

L C T LCT 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;
}

总结

L C T LCT LCT真是太好打了……
看到类似于染色的题目,不要忘了 L C T LCT LCT啊……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值