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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM