【牛客挑战赛46】F.柠檬树

20 篇文章 0 订阅
4 篇文章 0 订阅
这是一个关于数据结构与算法的问题,描述了在一颗包含n个柠檬的树上,牛牛和牛妹如何选择柠檬送给朋友的策略。牛妹根据每个朋友的需求选择区间内的柠檬,而牛牛需要确定最少的树枝数量以实现区间连通。解决方案涉及到两种方法:一种是使用分治与并查集,另一种是利用LCT(线段树)。博客中详细解释了这两种方法的实现过程,并给出了C++代码示例。
摘要由CSDN通过智能技术生成

Description

  • 这是一棵有 n 个柠檬的柠檬树,由 n-1 条枝条连接而成。

  • 秋天了,柠檬都成熟了, 牛牛和牛妹准备选一些柠檬送给他们的朋友们。

  • 对于每一个朋友,牛妹会选择第 l-r 个柠檬送给朋友。具体的采摘方法是:选取尽可能少的树枝,使得区间内的柠檬两两连通

  • 牛牛负责派送柠檬,但他的朋友太多啦,他实在是忙的上气不接下气,所以他想让您来帮忙。

  • n , q ≤ 2 e 5 n,q\le2e5 n,q2e5

Solution

  • 类似数星星
  • 同样有两种方法,首先先转化为到根节点的链并,然后减去lca的深度。
  • 其一即用分治,建出虚树,然后分别对于左边和右边的部分分别用一个并查集做,得出虚树上每一条边在那些时候会出现,然后是一个二维偏序。
  • 另一种即LCT。考虑我们对于区间[l,r],树上的每一条边用区间中编号最小的点来覆盖,从小到大加入点,对于一个点 x x x,它的祖先的整个链上,有一段段链被 y y y覆盖,这一段链只有当 y < l ≤ x y<l\le x y<lx时才会在 x x x的时候计算,因此我们按照右端点离线询问,然后扫一遍过去用树状数组维护每一个左端点的答案,用LCT的access来完成查询一段段链的这个操作。
  • 太久没打LCT了练练手,注意splay的时候判断的是当前点是否是根,但不能直接判是否它的父亲为0.
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 200005
using namespace std;

int n,q,i,j,k,ans[maxn],tot;
struct quet{int l,r,i;} que[maxn];
int cmp(quet a,quet b){return a.r<b.r;}

struct Treearray{
	int s[maxn];
	void add(int x,int d){for(;x<=n;x+=x&-x) s[x]+=d;}
	void addv(int l,int r,int d){add(l,d),add(r+1,-d);}
	int sum(int x,int S=0){for(;x;x-=x&-x) S+=s[x];return S;}
} T;

int em,e[maxn*2],nx[maxn*2],ls[maxn];
void insert(int x,int y){
	em++; e[em]=y; nx[em]=ls[x]; ls[x]=em;
	em++; e[em]=x; nx[em]=ls[y]; ls[y]=em;
}

namespace LCA{
	#define maxp 20
	int dfn[maxn],tot,Idfn[maxn],fa[maxn][maxp],dep[maxn];
	void dfs(int x,int p){
		fa[x][0]=p,dep[x]=dep[p]+1,dfn[x]=++tot,Idfn[tot]=x;
		for(int i=1;i<maxp;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
		for(int i=ls[x];i;i=nx[i]) if (e[i]!=p)
			dfs(e[i],x);
	}
	int t0[maxn*4],t1[maxn*4];
	void maketree(int x,int l,int r){
		if (l==r){t0[x]=t1[x]=dfn[l];return;}
		int mid=(l+r)>>1;
		maketree(x<<1,l,mid),maketree(x<<1^1,mid+1,r);
		t0[x]=min(t0[x<<1],t0[x<<1^1]);
		t1[x]=max(t1[x<<1],t1[x<<1^1]);
	}
	int mx,mi;
	void find(int x,int l,int r,int L,int R){
		if (l>R||r<L) return;
		if (L<=l&&r<=R) {
			mi=min(mi,t0[x]),mx=max(mx,t1[x]);
			return;
		}
		int mid=(l+r)>>1;
		find(x<<1,l,mid,L,R),find(x<<1^1,mid+1,r,L,R);
	}
	void prepare(){dfs(1,0);maketree(1,1,n);}
	int getlca(int x,int y){
		if (dep[x]<dep[y]) swap(x,y);
		for(int i=maxp-1;i>=0;i--) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
		if (x==y) return x;
		for(int i=maxp-1;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
		return fa[x][0];
	}
	int deplca(int l,int r){
		mx=0,mi=1e9,find(1,1,n,l,r);
		int k=getlca(Idfn[mi],Idfn[mx]);
		return dep[k];
	}
}
using LCA::prepare;
using LCA::deplca;

struct node{int s[2],fa,sum,v,id;} t[maxn*2];
void dfs(int x,int p){
	for(int i=ls[x];i;i=nx[i]) if (e[i]!=p){
		tot++,t[tot].v=t[tot].sum=1;
		t[tot].fa=x,t[e[i]].fa=tot;
		dfs(e[i],x);
	}
}

int getc(int x){return t[t[x].fa].s[1]==x;}
int nroot(int x){return t[t[x].fa].s[0]==x||t[t[x].fa].s[1]==x;}
void upd(int x){t[x].sum=t[x].v+t[t[x].s[0]].sum+t[t[x].s[1]].sum;}
void rotate(int x){
	int y=t[x].fa,c=getc(x); t[x].id=t[y].id,t[y].id=0;
	t[y].s[c]=t[x].s[c^1]; if (t[y].s[c]) t[t[y].s[c]].fa=y;
	if (nroot(y)) t[t[y].fa].s[getc(y)]=x; t[x].fa=t[y].fa;
	t[x].s[c^1]=y,t[y].fa=x;
	upd(y),upd(x);
}
void splay(int x){
	while (nroot(x)){
		int y=t[x].fa;
		if (nroot(y)) {
			if (getc(y)==getc(x)) rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
void access(int x){
	int y=0,k=x;
	while (x){
		splay(x);
		T.addv(t[x].id+1,k,t[x].sum-t[t[x].s[1]].sum);
		int tmp=T.sum(5);
		if (t[x].s[1]) t[t[x].s[1]].id=t[x].id;
		t[x].s[1]=y,upd(x);
		y=x,x=t[x].fa;
	}
	splay(k),t[k].id=k;
}

int main(){
	freopen("ceshi.in","r",stdin);
	scanf("%d%d",&n,&q),tot=n;
	for(i=1;i<n;i++) scanf("%d%d",&j,&k),insert(j,k);
	prepare(),dfs(1,0);
	for(i=1;i<=q;i++) scanf("%d%d",&que[i].l,&que[i].r),que[i].i=i;
	sort(que+1,que+1+q,cmp);
	j=1;
	for(i=1;i<=n;i++){
		access(i);
		for(;j<=q&&que[j].r==i;j++)
			ans[que[j].i]=T.sum(que[j].l)-deplca(que[j].l,que[j].r)+1;
	}
	for(i=1;i<=q;i++) printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值