1. 程式人生 > 實用技巧 >[USACO19DEC]Bessie's Snow Cow P 題解

[USACO19DEC]Bessie's Snow Cow P 題解

題目連結https://www.luogu.com.cn/problem/P5852

題意:

子樹染色,子樹詢問每個點的顏色數之和,一個點可以有多種顏色。 \((1\le N,Q\le10^5)\)

題解:

首先來看看子樹染一次色有什麼貢獻(假設這棵樹還沒染過這種顏色)。

  • 對於這個子樹內部的點,是所有點子樹內的點都有了這種顏色,因此對這些點的貢獻是子樹大小。

  • 其餘點只有是祖先的才會有貢獻,並且貢獻都是這個點的子樹大小。

這些東西可以開兩個樹狀陣列維護,一個查有多少祖先用顏色覆蓋了他(第一種),再乘個子樹大小,另一個直接在自己這裡加上自己的子樹大小,查詢時查詢整棵子樹的貢獻。

要是染過這種顏色怎麼辦呢?如果兩點之間沒有祖孫關係,那是沒有影響的。如果有子孫關係,可以直接把子孫的貢獻去掉。

正確性顯然。而這個關係可以對每種顏色開一個 set 按 DFS序 維護,只有相鄰的點會有祖孫關係,前面是後面的祖先。

這種東西可以直接按進和出的編號來弄……現在突然發現出點的時候不用加一,這樣還可以順便維護子樹大小。另外,對於被修改的那個點兩種貢獻可能會重複,第二種不要算進去自身就好了……

時間複雜度: \(O(N\log N)\)

程式碼:

#include <bits/stdc++.h>
#define to e[x][i]
using namespace std;
typedef long long ll;
typedef set<int>::iterator SIT;
const int N=1e5+5;
int n,q,sum,dfi[N],dfo[N],rev[N];
vector<int> e[N];
set<int> s[N];
struct BIT{
	ll tree[N];
	inline int lb(int x) {return x&(-x);}
	inline void add(int x,int v) {while(x<=n) {tree[x]+=v;x+=lb(x);}}
	inline ll ask(int x) {ll res=0;while(x) {res+=tree[x];x-=lb(x);}return res;}
}t1,t2;
inline SIT pre(SIT x) {return --x;}
inline int upd(int x,int v) {t1.add(dfi[x],v);t1.add(dfo[x]+1,-v);t2.add(dfi[x],(dfo[x]-dfi[x]+1)*v);}
void dfs(int x,int fa){
	rev[dfi[x]=++sum]=x;
	for(int i=0;i<e[x].size();i++)
		if(to!=fa) dfs(to,x);
	dfo[x]=sum;
}
signed main(){
	scanf("%d%d",&n,&q);
	for(int i=1,u,v;i<n;i++) {scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}
	dfs(1,0);
	while(q--){
		int op,x,y;scanf("%d%d",&op,&x);
		if(op==1){
			scanf("%d",&y);SIT it=s[y].upper_bound(dfi[x]);
			if(it!=s[y].begin()&&dfo[rev[*pre(it)]]>=dfo[x]) continue;
			while(it!=s[y].end()&&(*it)<=dfo[x]) {upd(rev[*it],-1);s[y].erase(it++);}
			s[y].insert(dfi[x]);upd(x,1);
		}
		else printf("%lld\n",t1.ask(dfi[x])*(dfo[x]-dfi[x]+1)+t2.ask(dfo[x])-t2.ask(dfi[x]));
	}
	return 0;
}