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补了一天?)