[USACO19DEC]Bessie's Snow Cow P 題解
阿新 • • 發佈:2020-11-02
題目連結: 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; }