BZOJ 3589 动态树 树链拆分+纳入和排除定理

标题效果:鉴于一棵树。每个节点有一个右值,所有节点正确启动值他们是0。有两种操作模式,0 x y代表x右所有点的子树的根值添加y。

1 k a1 b1 a2 b2 ……ak bk代表质疑。

共同拥有者k边缘( k <= 5),这些边保证是一个点到根节点的路径上的一段。

问这些路径段上的点的权值和是多少,可能有多条路径重叠的情况。

思路:子树改动,区间查询,非常明显用树链剖分解决,树链剖分维护一个size域。那么x的子树的范围就是pos[x]到pos[x] + size[x] - 1这一段上。能够用线段树区间改动。

关键是查询的时候。单查一条链肯定没什么问题。

可是假设几条链有交集的话就麻烦了。可是依据容斥原理我们知道,当我们把全部的路径都加过一次之后,两个路径重合的部分就计算重了。减掉。之后三个路径重合的部分减多了。再加上……我们仅仅要求出单个链的,两个路径重合的部分,三个路径重合的部分……这样就能够知道答案了。

怎样求多个链相交呢?我们先考虑两个链相交。因为每个路径保证是一个点到根节点的路径上的一段,那么两个链相交仅仅有一种情况。

例如以下图。

链1 2 3 4和2 3 5 6的交集就是2 3 。

观察一下。事实上3是4和6的LCA。2是两个链的顶端较深的那一个。不存在交集的情况就是链底的LCA深度深于当中的一条链的链顶。

多画几个图发现真的是这样。(事实上我仅仅是不会证明罢了。

。。

然后从1到1 << 5枚举取全部边的情况,计算取到n条边时的相交情况,n是计数就加上,是偶数就减去。

CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 200010
#define LEFT (pos << 1)
#define RIGHT (pos << 1|1)
#define CNT (r - l + 1)
using namespace std;

pair<int,int> ask[MAX];

struct SegmentTree{
	int sum,c;
}tree[MAX << 2];

int points,asks;
int head[MAX],total;
int next[MAX],aim[MAX];

int son[MAX],size[MAX],father[MAX],deep[MAX];
int pos[MAX],top[MAX],cnt;

inline void Add(int x,int y)
{
	next[++total] = head[x];
	aim[total] = y;
	head[x] = total;
}

void PreDFS(int x,int last)
{
	father[x] = last;
	deep[x] = deep[last] + 1;
	size[x] = 1;
	for(int i = head[x]; i; i = next[i]) {
		if(aim[i] == last)	continue;
		PreDFS(aim[i],x);
		size[x] += size[aim[i]];
		if(size[aim[i]] > size[son[x]])
			son[x] = aim[i];
	}
}

void DFS(int x,int _top)
{
	pos[x] = ++cnt;
	top[x] = _top;
	if(son[x])	DFS(son[x],_top);
	for(int i = head[x]; i; i = next[i]) {
		if(aim[i] == father[x] || aim[i] == son[x])	continue;
		DFS(aim[i],aim[i]);
	}
}

inline void PushDown(int pos,int cnt)
{
	if(tree[pos].c) {
		tree[LEFT].c += tree[pos].c;
		tree[RIGHT].c += tree[pos].c;
		tree[LEFT].sum += tree[pos].c * (cnt - (cnt >> 1));
		tree[RIGHT].sum += tree[pos].c * (cnt >> 1);
		tree[pos].c = 0;
	}
}

void Modify(int l,int r,int x,int y,int pos,int c)
{
	if(l == x && r == y) {
		tree[pos].c += c;
		tree[pos].sum += CNT * c;
		return ;
	}
	PushDown(pos,CNT);
	int mid = (l + r) >> 1;
	if(y <= mid)	Modify(l,mid,x,y,LEFT,c);
	else if(x > mid)	Modify(mid + 1,r,x,y,RIGHT,c);
	else {
		Modify(l,mid,x,mid,LEFT,c);
		Modify(mid + 1,r,mid + 1,y,RIGHT,c);
	}
	tree[pos].sum = tree[LEFT].sum + tree[RIGHT].sum;
}

int Ask(int l,int r,int x,int y,int pos)
{
	if(l == x && r == y)
		return tree[pos].sum;
	PushDown(pos,CNT);
	int mid = (l + r) >> 1;
	if(y <= mid)	return Ask(l,mid,x,y,LEFT);
	if(x > mid)		return Ask(mid + 1,r,x,y,RIGHT);
	int left = Ask(l,mid,x,mid,LEFT);
	int right = Ask(mid + 1,r,mid + 1,y,RIGHT);
	return left + right;
}

inline int Ask(int x,int y)
{
	int re = 0;
	while(top[x] != top[y]) {
		if(deep[top[x]] < deep[top[y]])
			swap(x,y);
		re += Ask(1,cnt,pos[top[x]],pos[x],1);
		x = father[top[x]];
	}
	if(deep[x] < deep[y])	swap(x,y);
	re += Ask(1,cnt,pos[y],pos[x],1);
	return re;
}

inline int GetLCA(int x,int y)
{
	while(top[x] != top[y]) {
		if(deep[top[x]] < deep[top[y]])
			swap(x,y);
		x = father[top[x]];
	}
	return deep[x] < deep[y] ? x:y;
}

inline bool Merge(pair<int,int> &a,pair<int,int> b)
{
	if(deep[a.first] < deep[a.second])	swap(a.first,a.second);
	if(deep[b.first] < deep[b.second])	swap(b.first,b.second);
	int lca = GetLCA(a.first,b.first);
	if(deep[a.second] > deep[lca] || deep[b.second] > deep[lca])	return false;
	a.first = lca;
	a.second = deep[a.second] > deep[b.second] ? a.second:b.second;
	return true;
}

inline int Calc(int cnt,int status)
{
	int p = 0;
	for(int i = 0; i < cnt; ++i)
		p += (status >> i)&1;
	p = p&1 ? 1:-1;
	pair<int,int> now(0,0);
	for(int i = 0; i < cnt; ++i)
		if((status >> i)&1) {
			if(!now.first)	now = ask[i + 1];
			else if(!Merge(now,ask[i + 1]))	return 0;
		}
 	//cout << status << ' ' << now.first << ' ' << now.second << ' ' << Ask(now.first,now.second) << ' ' << p << endl;
	return p * Ask(now.first,now.second);
}

inline int MainTask(int cnt)
{
	int re = 0;
	for(int i = 1; i <= cnt; ++i)
		scanf("%d%d",&ask[i].first,&ask[i].second);
	for(int i = 1; i < (1 << cnt); ++i)
		re += Calc(cnt,i);
	return re;
}

int main()
{
	cin >> points;
	for(int x,y,i = 1; i < points; ++i) {
		scanf("%d%d",&x,&y);
		Add(x,y);
	}
	PreDFS(1,0);
	DFS(1,1);
	cin >> asks;
	for(int flag,i = 1; i <= asks; ++i) {
		scanf("%d",&flag);
		if(!flag) {
			int x,y;
			scanf("%d%d",&x,&y);
			Modify(1,cnt,pos[x],pos[x] + size[x] - 1,1,y);
		}
		else {
			int cnt;
			scanf("%d",&cnt);
			printf("%d\n",MainTask(cnt)&0x7fffffff);
		}
	}
	return 0;
}

版权声明:本文博客原创文章,博客,未经同意,不得转载。

时间: 2024-08-04 14:16:05

BZOJ 3589 动态树 树链拆分+纳入和排除定理的相关文章

BZOJ 3589 动态树 树链剖分+容斥原理

题目大意:给定一棵以1为根的有根树,每个节点有点权,提供两种操作: 1.以某个节点为根的子树所有节点权值+x 2.求一些链的并集的点权和,其中这些链都是由某个节点出发指向根 首先子树修改,链上查询,树链剖分的WT~ 然后这些链上的每个点的点权都只能被加一次,肯定不能打标记,由于k<=5,我们考虑容斥原理 总权值=单链-两两之交+三链之交-- 状压枚举即可 两条链的交集求法如下: 1.求两条链底的LCA 2.若LCA的深度小于其中一条链的链顶深度 交集为空 返回0 3.返回一条链 链底为LCA 链

BZOJ 3589 动态树 树链剖分+容斥定理

题目大意:给出一棵树,每一个节点有一个权值,一开始所有节点的权值都是0.有两种操作,0 x y代表以x为根节点的子树上所有点的权值增加y.1 k a1 b1 a2 b2 --ak bk代表询问.一共有k条边( k <= 5),这些边保证是一个点到根节点的路径上的一段.问这些路径段上的点的权值和是多少,可能有多条路径重叠的情况. 思路:子树修改,区间查询,很明显用树链剖分解决,树链剖分维护一个size域,那么x的子树的范围就是pos[x]到pos[x] + size[x] - 1这一段上,可以用线

bzoj 3589: 动态树【树链剖分+容斥】

因为一开始调试不知道unsigned怎么输出就没有加\n结果WA了一上午!!!!!然而最后放弃了unsigned选择了&2147483647 首先链剖,因为它所给的链一定是某个点到根的路径上的一段(一开始没看到),也就是说链是不会拐弯的,那么考虑容斥,加上每条链的长度减去两条链的交的长度加上三条链的交的长度... 关于求链的交,因为链不会拐弯,所以对于两条链上深度较深的两个点\( (v_1,v_2) \)求\( lca \),如果\( lca \)的深度小于两条链的较浅点的任意一个,那么这两条链

【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)

3589: 动态树 Time Limit: 30 Sec  Memory Limit: 1024 MBSubmit: 405  Solved: 137[Submit][Status][Discuss] Description 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0: 这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1: 小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝

BZOJ 2243: [SDOI2011]染色 树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1886  Solved: 752[Submit][Status] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. In

【BZOJ】3319: 黑白树

http://www.lydsy.com/JudgeOnline/problem.php?id=3319 题意:给一棵n节点的树(n<=1e6),m个操作(m<=1e6),每次操作有两种:1.查询u到根的第一条黑边的编号.2.将u到v的路径全部染成黑色 #include <cstdio> #include <cstring> #include <cmath> #include <string> #include <iostream>

acm 2015北京网络赛 F Couple Trees 主席树+树链剖分

提交 题意:给了两棵树,他们的跟都是1,然后询问,u,v 表 示在第一棵树上在u点往根节点走 , 第二棵树在v点往根节点走,然后求他们能到达的最早的那个共同的点 解: 我们将第一棵树进行书链剖,然后第二棵树采用主席树,他的信息来自他的父亲节点,每个点存他在第一棵树 树链剖分后的位置,这样我们每次查询uv的时候我们只要 我们选取u和top[u]这段区间在主席树v这颗树上找,在这个区间能取到的最大值,一旦存在,这个最大值就我们要的,这个点保存着他到根节点这条路上所有点在第一棵树剖分后的位置 #inc

BZOJ 1912 巡逻(树直径)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1912 题意:给出一棵树,边权为1.现在加一条或两条边后,使得从1出发遍历每个点至少一次再回到1的路程最短. 思路:先求一次树的直径Max1.然后将直径的边权改为-1,再求一次直径Max2.答案为ans=(n-1)*2-(Max1-1)-(Max2-1). struct node { int u,v,w,next; }; node edges[N<<1]; int head[N],e;

在Asp.net core使用配置Json创建动态目录树

一.前言 使用动态目录树可以使左边栏中的目录更加灵活,本文介绍如何将目录保存在json配置文件中,再读取出来经过处理后生成目录树. 二.数据结构 1. TreeMenuNode类名 将TreeMenuNode类设置成与json格式相对应,注意Children应为List类型.目录的内容包括MenuName,Action,Controller,Icon public class TreeMenuNode2 { public int MenuNumber { get; set; } public s