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-10-11 22:11:39

BZOJ 3589 动态树 树链剖分+容斥定理的相关文章

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

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

AtCoder Beginner Contest 152 - F - Tree and Constraints (容斥定理+树上路径的性质)

AtCoder Beginner Contest 152 - F - Tree and Constraints (容斥定理+树上路径的性质) We have a tree with NN vertices numbered 11 to NN. The ii-th edge in this tree connects Vertex aiai and Vertex bibi. Consider painting each of these edges white or black. There ar

容斥定理 hdu2204 Eddy&#39;s爱好

传送门:点击打开链接 很明显会有大量重复的被计算,所以很容易就想到容斥定理. 我们设dp[i]表示能表示成M^i(i>1)且i是这个数字能表示出来的最大的情况时的总类数 比如,27拆成M^K时的K最大能表示成3,所以27这个数字分在dp[3]这一类 1我们暂时不考虑,不把它放在任何一类 因为K>1,所以K至少是2,最大是2^K=N的时候,所以K最大等于log2(N),所以K非常的小 首先,求出K最大大概的位置 然后开始求dp[i].求法如下: 首先,1~N中有哪些是能拆分成M^i的,利用pow

cf451E Devu and Flowers 卢卡斯定理+容斥定理

题目:http://codeforces.com/problemset/problem/451/E 题意:有n个盒子(n<=20),每个盒子中有10^12个小球,现从每个盒子中取出若干球(可为0),求共取出s个小球(s<=10^14)的方案数. 组合数学问题,求C(n,m).但n,m过大时,可用卢卡斯定理. 卢卡斯定理:C(n,m) %p = C(n/p,m/p) * C(n%p,m%p) 从n个盒子中取出s个球的方案数,相当于插板,即 C(s+n-1,n-1).注意这是没有限制条件的情况.

HDU1796 How many integers can you find【容斥定理】

题目链接: http://acm.hdu.edu.cn/showproblem.php? pid=1796 题目大意: 给你一个整数N.和M个整数的集合{A1.A2.-.Am}.集合内元素为非负数(包括零),求小于N的 正整数(1~N-1)中,能被M个整数的集合中随意一个元素整除的正整数个数. 比如N = 12.M = {2,3},在1~N-1中,能被2整除的数为{2,4,6.8.10},能被3整除的数为 {3.6,9}.则所求集合为{2,3,4.6,8,9,10},共7个,则答案为7. 思路:

HDU 1796 How many integers can you find (容斥定理 + 二进制)

How many integers can you find Time Limit: 12000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 5493    Accepted Submission(s): 1567 Problem Description Now you get a number N, and a M-integers set, you should

HDU 1695 GCD(容斥定理)

GCD Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 7529    Accepted Submission(s): 2773 Problem Description Given 5 integers: a, b, c, d, k, you're to find x in a...b, y in c...d that GCD(x, y

GCD SUM 强大的数论,容斥定理

GCD SUM Time Limit: 8000/4000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) SubmitStatus Problem Description 给出N,M执行如下程序:long long  ans = 0,ansx = 0,ansy = 0;for(int i = 1; i <= N; i ++)   for(int j = 1; j <= M; j ++)       if(gcd(i,j)

HDU5768Lucky7(中国剩余定理+容斥定理)(区间个数统计)

When ?? was born, seven crows flew in and stopped beside him. In its childhood, ?? had been unfortunately fall into the sea. While it was dying, seven dolphins arched its body and sent it back to the shore. It is said that ?? used to surrounded by 7