Bzoj3589:动态树:树链剖分+容斥原理+树链的并

10 篇文章 0 订阅
5 篇文章 0 订阅

题目链接:3589:动态树

子树修改,树链查询,一看就知道是树链剖分的题

关键在于容斥,对于一堆链,如果有偶数个就加上,奇数个就减去,所以枚举子集就可以了

对于两条链,我们怎么求交?设两条链中最深的节点为a,b

求出他们的LCA,如果LCA的深度小于其中一条链的最浅节点的深度,交集为空,否则交集为LCA到深度较大的链顶形成的一条链

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=800010;
int n,m,tot=0,h[maxn],pos[maxn];
struct edge{int to,next;}G[maxn*5];
int dep[maxn],size[maxn],bin[maxn];
int fa[maxn][21],Belong[maxn],ind=0;
struct point{int a,b;}p[1000];
struct seg{
	int l,r,data,sum;
	seg *lc,*rc;
	seg():data(0),sum(0){}
};
seg *root=new seg();
bool vis[maxn];

void add(int x,int y){
	G[++tot].to=y; G[tot].next=h[x]; h[x]=tot;
}

void dfs1(int x,int deep){
    dep[x]=deep; size[x]=1; vis[x]=1;
    for (int i=1;i<=19;++i){
        if (dep[x]<(1<<i)) break;
        fa[x][i]=fa[fa[x][i-1]][i-1];
    }
    for (int i=h[x];i;i=G[i].next){
        int v=G[i].to;
        if (vis[v]) continue;
        fa[v][0]=x;
        dfs1(v,deep+1);
        size[x]+=size[v];
    }
}

void dfs2(int x,int L){
    int k=0; ++ind; Belong[x]=L; pos[x]=ind;
    for (int i=h[x];i;i=G[i].next)
       if (dep[x]<dep[G[i].to]&&size[G[i].to]>size[k])
          k=G[i].to;
    if (!k) return; dfs2(k,L);
    for (int i=h[x];i;i=G[i].next)
       if (dep[x]<dep[G[i].to]&&k!=G[i].to)
          dfs2(G[i].to,G[i].to);
}

void push_up(seg *p){
	if (p->l+1==p->r) return; p->sum=0;
	if (p->lc) p->sum+=p->lc->sum;
	if (p->rc) p->sum+=p->rc->sum;
}

void build(seg *p,int l,int r){
	p->l=l; p->r=r;
	if (l+1==r){
		p->lc=p->rc=NULL;return;
	}else if (l+1<r){
		int mid=(l+r)>>1;
		p->lc=new seg();
		p->rc=new seg();
		if (l<mid) build(p->lc,l,mid);
		else p->lc=NULL;
		if (mid<r) build(p->rc,mid,r);
		else p->rc=NULL;
	}
}

int query(int a,int b){
    if (dep[a]<dep[b]) swap(a,b);
    int t=dep[a]-dep[b];
    for (int i=19;~i;--i)
       if (t&(1<<i)) a=fa[a][i];
    for (int i=19;~i;i--)
       if (fa[a][i]!=fa[b][i]){
           a=fa[a][i];b=fa[b][i];}
    if (a==b) return a;
    else return fa[a][0];
}

void push_down(seg *p){
	if (p->l+1==p->r) return;
	if (p->lc) p->lc->sum+=(p->lc->r-p->lc->l)*p->data,
			   p->lc->data+=p->data;
	if (p->rc) p->rc->sum+=(p->rc->r-p->rc->l)*p->data,
			   p->rc->data+=p->data;
	p->data=0;
}

void update(seg *p,int l,int r,int v){
	push_down(p);
	if (l<=p->l&&p->r<=r) {
		int len=p->r-p->l; p->sum+=v*len;
		p->data+=v; return;
	}else{
		int mid=(p->l+p->r)>>1;
		if (l<mid) update(p->lc,l,r,v);
		if (mid<r) update(p->rc,l,r,v);
		push_up(p);
	}
}

int ask(seg *p,int l,int r){
	push_down(p);
	if (l<=p->l&&p->r<=r) return p->sum;
	else{
		int mid=(p->l+p->r)>>1,ret=0;
		if (l<mid) ret+=ask(p->lc,l,r);
		if (mid<r) ret+=ask(p->rc,l,r);
		return ret;
	}
}

int getnum(int x,int y){
    int sum=0;
    while (Belong[x]!=Belong[y]){
		if (dep[Belong[x]]<dep[Belong[y]]) swap(x,y);
        sum+=ask(root,pos[Belong[x]],pos[x]+1);
        x=fa[Belong[x]][0];
    } if (dep[x]<dep[y]) swap(x,y);
	sum+=ask(root,pos[y],pos[x]+1);
    return sum;
}

point getdel(point a,point b){
	int lca=query(a.a,b.a); point ret; 
	if (dep[lca]<dep[a.b]||dep[lca]<dep[b.b]){
		ret.a=ret.b=-1; return ret;
	}ret.a=lca;ret.b=dep[a.b]>dep[b.b]?a.b:b.b;
	return ret;
}

int calc(int x){
	point tmp; tmp.a=tmp.b=0;
	for (int i=1;x;i++,x>>=1) if (x&1){
		if (!tmp.a) tmp=p[i];
		else tmp=getdel(tmp,p[i]);
		if (tmp.a==-1) return 0;
	}return getnum(tmp.a,tmp.b);
}

void getans(int k){
	int ans=0;
	for (int i=1;i<(1<<k);++i)
		ans+=calc(i)*(bin[i]&1?1:-1);
	printf("%d\n",ans&2147483647);
}

int main(){
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int x,y; scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	}
	dfs1(1,1); dfs2(1,1);
	build(root,1,n+1);
	for (int i=1;i<1<<5;++i) bin[i]=bin[i>>1]+(i&1);
	scanf("%d",&m);
	for (int i=1;i<=m;++i){
		int opt,x,y,k;
		scanf("%d",&opt);
		if (!opt){
			scanf("%d%d",&x,&y);
			update(root,pos[x],pos[x]+size[x],y);
		}else if (opt){
			scanf("%d",&k);
			for (int j=1;j<=k;++j){
				p[j].a=p[j].b=0;
			    scanf("%d%d",&p[j].a,&p[j].b);
			    if (dep[p[j].a]<dep[p[j].b]) swap(p[j].a,p[j].b);
			}getans(k);
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值