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