2021“MINIEYE杯”中国大学生算法设计超级联赛 第二场题解

ACM暑期多校联合赛 题解

 

6962 I love tree

题意:给你一棵树,然后有两个操作

①给定两个结点a,b,使得a->b路径上的所有节点的权值加上x^2(x = 1,2,3,4....)

②询问节点x的权值大小

思路:

“肯定是熟练泼粪!”

作为一颗有理想的树,只有当它连续的时候我们才好操作,So,熟练泼粪(树链剖分)应运而生...

这里不对树链剖分做详细介绍,只需知道它能够让对一棵树的离散操作变成连续操作即可...

所以现在,我们可以将树上离散的操作转化为对于连续区间的操作了!

嗯....还不是很好做...我们先考虑在线性结构上进行连续区间加x^2该如何做

举个栗子:

现在我们有一段长为5的序列,其中的值都是0~

然后我们对[2,5]这一段执行①操作,序列变成->

[0,1,4,9,16]

然后我们再对[1,3]执行①操作,序列变成->

[1,5,13,9,16]

OK,到现在相信你已经明白了这个①操作到底在干嘛。然后我们来考虑这样一个事情:

首先排除暴力区间修改

那怎么办?区间加的数不一样,真烦

这时候,有个非常NB的技巧,将一段区间维护成一段函数

比如,如果我们对[2,5]执行①操作时,这时候这个区间上每个点不就是这个函数形成的吗?

此时

将其展开,我们会得到

到这里应该茅塞顿开了(并没有)

现在,如果出现了区间修改,我们只需要维护这个函数的系数即可!也就是,我们用三个数组来维护x^2,x和常数就行

然后对于下标为 i 的值,它就是i*i*a + i*b + c啦!!!

代码:

#include <iostream>
#define lowbit(x) (x&(-x))
using namespace std;
void Swap(int &x,int &y)
{
	int t = x;
	x = y;
	y = t;
}
const int MAXN = 1e5 + 7;
int a[MAXN],b[MAXN],c[MAXN];
typedef long long ll;
int n;
void add(int s[],int x,ll k)
{
	while(x <= n)
	{
		s[x] += k;
		x += lowbit(x);
	}
}
ll Sum(int s[],int x)
{
	ll ans = 0;
	while(x)
	{
		ans += s[x];
		x -= lowbit(x);
	}
	return ans;
}//维护函数 (x - L)^2 在区间[L,R]内 
void modify(int s[],int le,int ri,ll x)
{
	add(s,le,x);
	add(s,ri + 1,-x);
}
/*
3
2
3 1 1
1 2 7
*/
int main()
{
	scanf("%d",&n);
	
	int m;
	scanf("%d",&m);
	while(m--)
	{
		int le,ri,x;//[le,ri]依次加上 x^2 , (x + 1)^2 , (x + 2)^2... 
		scanf("%d %d %d",&le,&ri,&x);
		if(ri >= le)
		{
			modify(a,le,ri,1);
			modify(b,le,ri,2*(x - le));
			modify(c,le,ri,1ll*(le - x)*(le - x));
		}
		else 
		{
			Swap(le,ri);
			modify(a,le,ri,1);
			modify(b,le,ri,-2*(x + ri));
			modify(c,le,ri,1ll*(ri + x)*(ri + x));
		}
	}
	
	for(int i = 1;i <= n;i++)
	{
		ll ans = 0;
		ans += Sum(a,i)*i*i;
		ans += Sum(b,i)*i;
		ans += Sum(c,i);
		printf("%d -> %d\n",i,ans);
	}
	return 0;
}

由于是区间的操作,我们可以通过树状数组转化为对点的操作(差分即可...)

上面的代码中与前面讲扩展了一点,我们可以从输入的x^2来进行叠加...而不用固定从1^2开始~

这也是这个Tree中需要用到的...

“讲了一大堆没用的玩意”

------------------------------------------------------------------------------正文分界线------------------------------------------------------------------------------------

So,区间的修改操作我们也会了,那么这个题不就是变为树上操作么?这?不就直接套板子?(逃

点击查看代码

#include <iostream>
#define lowbit(x) (x&(-x))
using namespace std;
const int MAXN = 2e5 + 7;
typedef long long ll;
struct node
{
	int ne,to,w;
}a[MAXN];
int head[MAXN],cnt = 0;
void add(int x,int y,int w = 0)
{
	a[++cnt].ne = head[x];
	head[x] = cnt;
	a[cnt].to = y;
	a[cnt].w = w;
}
int size[MAXN],son[MAXN],d[MAXN];
int top[MAXN];
int f[MAXN],dfn;
int nid[MAXN],oid[MAXN];
void dfs1(int x,int fa)
{
	size[x] = 1;son[x] = 0;
	f[x] = fa;d[x] = d[fa] + 1;
	for(int i = head[x];i;i = a[i].ne)
	{
		int to = a[i].to;
		if(to == fa)	continue;
		dfs1(to,x);
		size[x] += size[to];
		if(size[son[x]] < size[to])	son[x] = to; 
	}
}
void dfs2(int x,int fa)
{
	top[x] = fa;
	nid[x] = ++dfn;
	oid[dfn] = x;
	if(son[x])	dfs2(son[x],fa);
	for(int i = head[x];i;i = a[i].ne)
	{
		int to = a[i].to;
		if(to != f[x] && to != son[x])
		dfs2(to,to); 
	}
}
void Swap(int &x,int &y)
{int t = x;x = y;y = t;}
int lca(int x,int y)
{
	while(top[x] != top[y])
	{
		if(d[top[x]] < d[top[y]])	y = f[top[y]];
		else	x = f[top[x]];
	}
	return d[x] > d[y] ? y:x;
}
ll d0[MAXN],d1[MAXN],d2[MAXN];
int n; 
void add(ll s[],int x,ll k)
{
	while(x <= n)
	{
		s[x] += k;
		x += lowbit(x);
	}
}
ll Sum(ll s[],int x)
{
	ll ans = 0;
	while(x)
	{
		ans += s[x];
		x -= lowbit(x);
	}
	return ans;
} 
void modify(ll s[],int le,int ri,ll k)
{
	add(s,le,k);
	add(s,ri + 1,-k);
}
void Go(int x,int y,int len)
{
	int be = 1,End = len; 
	while(top[x] != top[y])
	{
		if(d[top[x]] > d[top[y]])
		{//跳x , 本就是从x开始加的,于是初始的是1开始 
			int end_idx = nid[x];//一条链上的开始端,下标较大 
			int be_idx = nid[top[x]];//结束端,下标较小 
			//现在就是 从  be_idx -> end_idx 每个分别加上 be^2,(be + 1)^2,...,...
//			Swap(be_idx,end_idx);
			modify(d0,be_idx,end_idx,1ll*(end_idx + be)*(end_idx + be));
			modify(d1,be_idx,end_idx,-(be + end_idx));
			modify(d2,be_idx,end_idx,1);
			x = f[top[x]];
			be += (end_idx - be_idx + 1);//从新编号开始加了 
		}
		else
		{//跳y, 那么就是反向加, 从最大的那个开始加 
			int be_idx = nid[top[y]];//下标较小 
			int end_idx = nid[y];//下标较大 
			//还是同上一样的操作,不过这里的be_idx一定是从top开始的,因为越往上dfs序越小 
			//即从be_idx -> end_idx 每个分别加上(End - len + 1), (End - len),...,(End)
			
			int now = End - (end_idx - be_idx);//开始加的那个值 
			modify(d0,be_idx,end_idx,1ll*(be_idx - now)*(be_idx - now));
			modify(d1,be_idx,end_idx,(now - be_idx));
			modify(d2,be_idx,end_idx,1);
			y = f[top[y]];
			End = now - 1;//新的末尾编号 
		}
	}
	if(d[x] <= d[y])
	{//此时x的新下标较小(其实y现在在x的子树下面),累加即为nid[x] -> nid[y]区间 
		int be_idx = nid[x];//下标较小 
		int end_idx = nid[y];//下标较大 
		modify(d0,be_idx,end_idx,1ll*(be_idx - be)*(be_idx - be));
		modify(d1,be_idx,end_idx,(be - be_idx));
		modify(d2,be_idx,end_idx,1);
	}
	else
	{//此时y在x的上面,即x是y子树上的一个节点 
		int be_idx = nid[y];//下标较小 
		int end_idx = nid[x];//下标较大 
		modify(d0,be_idx,end_idx,1ll*(end_idx + be)*(end_idx + be));
		modify(d1,be_idx,end_idx,-(be + end_idx));
		modify(d2,be_idx,end_idx,1);
	}
}
/*
7
1 2
1 5
2 3
3 4
3 7
5 6

100
1 6 1
2 1 
2 5
2 6
*/
int main()
{
	scanf("%d",&n);
	cnt = 0;
	for(int i = 1;i < n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs1(1,0);
	dfs2(1,1);
//	for(int i = 1;i <= n;i++)
//	printf("%d -> %d\n",i,nid[i]);
	int q;
	scanf("%d",&q);
//	printf("%d %d\n",top[6],top[1]);
	while(q--)
	{
		int op;
		scanf("%d",&op);
		if(op == 1)
		{
			int x,y;
			scanf("%d %d",&x,&y);
			int t = lca(x,y);
			int Len = d[x] + d[y] - 2*d[t] + 1;//总长度 (1^2 + ... + Len^2
			Go(x,y,Len); 
		}
		else
		{
			int x;
			scanf("%d",&x);
			x = nid[x];
			ll na = Sum(d2,x);
			ll nb = Sum(d1,x);
			ll nc = Sum(d0,x);
			printf("%lld\n",x*x*na + x*2*nb + nc);
		}
	}
	return 0;
}

未完待续...(天啦撸,Tree补了一天?)

 

posted @ 2021-07-23 14:17  K0njac  阅读(412)  评论(4编辑  收藏  举报