洛谷 P3714 - [BJOI2017]樹的難題(點分治)
阿新 • • 發佈:2021-09-20
點分治+路徑合併的技巧
,其中 \(col_y\) 為 \(x\to y\) 路徑上經過的第一條邊的權值。看到這個 \([col_y=col_z]\) 貌似有點棘手,不過注意到我們貢獻顯然是一個子樹一個子樹計算的對吧,因此我們考慮將 \(x\) 所有子樹按 \(x\) 到這棵子樹經過的第一條邊的顏色從小到大排序,然後維護兩棵線段樹,第一棵線段樹上下標為 \(d\) 的位置上維護 \(\max\limits_{dep_y=d\land col_y\ne C}sum_y\),第二棵維護 \(\max\limits_{dep_y=d\land col_y=C}sum_y\),其中 \(C\) 為當前顏色種類,然後每次顏色改變就暴力地將第二棵線段樹中所有元素插入第一棵線段樹中即可,查詢就在兩棵樹中分別查 \([r-dep_y,l-dep_y]\) 的最大值,記作 \(mx1\) 和 \(mx2\),然後用 \(mx1+sum_y,mx2-c_{col_y}+sum_y\) 更新答案即可。
咦?鴿子 tzc 竟然來補題解了?incredible(
首先看到這樣類似於路徑統計的問題我們可以非常自然地想到點分治。每次我們找出每個連通塊的重心 \(x\) 然後以 \(x\) 為根 DFS 一遍整個子樹,我們假設 \(y\) 到 \(x\) 的距離為 \(dep_y\),\(x\to y\) 這一段上顏色的權值之和為 \(sum_y\),那麼考慮怎樣合併兩條路徑。顯然對於兩個在 \(x\) 不同子樹內的點 \(y,z\),\(y\to z\) 路徑上邊的個數就是 \(dep_y+dep_z\),路徑上權值之和就是 \(sum_y+sum_z-c_{col_y}·[col_y=col_z]\)
時間複雜度 \(n\log^2n\),其中一個 \(\log\) 在於點分治,一個 \(\log\) 在於線段樹。
最後稍微總結一下這類點分治解決樹上路徑計數題目的解題技巧:首先要考慮怎樣合併兩段路徑,如果不好合並那一般使用點分治不太好解決,其次要思考如何維護兩段路徑的決策,比較簡單的使用一個桶即可維護,比較複雜的需用 BIT/線段樹/平衡樹解決。對於計數問題,容斥也是一個不錯的選擇,即先不考慮兩個端點不在同一子樹這一條件,先一股腦把貢獻全加上去,再扣掉在同一子樹內的情況。
const int MAXN=2e5;
const int INF=0x3f3f3f3f;
const ll INFll=0x3f3f3f3f3f3f3f3fll;
int n,m,L,R,c[MAXN+5],hd[MAXN+5],to[MAXN*2+5],val[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v,int w){to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;}
int mx[MAXN+5],cent=0,siz[MAXN+5];bool vis[MAXN+5];
void findcent(int x,int f,int tot){
mx[x]=0;siz[x]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f||vis[y]) continue;
findcent(y,x,tot);siz[x]+=siz[y];
chkmax(mx[x],siz[y]);
} chkmax(mx[x],tot-siz[x]);
if(mx[cent]>mx[x]) cent=x;
}
int dep[MAXN+5];ll sum[MAXN+5];
void getdep(int x,int f,int pre){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];if(y==f||vis[y]) continue;
dep[y]=dep[x]+1;sum[y]=sum[x]+((z==pre)?0:c[z]);
getdep(y,x,z);
}
}
struct segtree{
struct node{int l,r;ll mx;} s[MAXN*4+5];
stack<int> stk;
void pushup(int k){s[k].mx=max(s[k<<1].mx,s[k<<1|1].mx);stk.push(k);}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].mx=-INFll;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void modify(int k,int p,ll v){
if(s[k].l==s[k].r) return chkmax(s[k].mx,v),stk.push(k),void();
int mid=s[k].l+s[k].r>>1;(p<=mid)?modify(k<<1,p,v):modify(k<<1|1,p,v);
pushup(k);
}
ll query(int k,int l,int r){
if(l>r) return -INFll;
if(l<=s[k].l&&s[k].r<=r) return s[k].mx;
int mid=s[k].l+s[k].r>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
void relax(){
while(!stk.empty()){
int k=stk.top();stk.pop();
s[k].mx=-INFll;
}
}
} s1,s2;
vector<int> pt;
ll res=-INFll;
void findpts(int x,int f){
pt.pb(x);
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f||vis[y]) continue;
findpts(y,x);
}
}
void divcent(int x){
// printf("divcent %d\n",x);
vis[x]=1;dep[x]=sum[x]=0;
vector<pii> sub;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];if(vis[y]) continue;
dep[y]=1;sum[y]=c[z];getdep(y,x,z);
sub.pb(mp(z,y));
} sort(sub.begin(),sub.end());
vector<int> wt;s1.modify(1,0,0);
for(int i=0;i<sub.size();i++){
if(i&&sub[i].fi!=sub[i-1].fi){
s2.relax();
for(int y:wt) s1.modify(1,dep[y],sum[y]);
wt.clear();
} int y=sub[i].se;pt.clear();findpts(y,x);
for(int z:pt){
int d=dep[z];
if(d<=R){
chkmax(res,s1.query(1,max(L-d,0),R-d)+sum[z]);
chkmax(res,s2.query(1,max(L-d,0),R-d)+sum[z]-c[sub[i].fi]);
}
} for(int z:pt) wt.pb(z),s2.modify(1,dep[z],sum[z]);
// for(int z:pt) printf("%d %d %lld\n",z,dep[z],sum[z]);
} s1.relax();s2.relax();
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(vis[y]) continue;cent=0;
findcent(y,x,siz[y]);divcent(cent);
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&L,&R);s1.build(1,0,n);s2.build(1,0,n);
for(int i=1;i<=m;i++) scanf("%d",&c[i]);
for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),adde(u,v,w),adde(v,u,w);
mx[0]=INF;findcent(1,0,n);divcent(cent);
printf("%lld\n",res);
return 0;
}