兩個都給出點分治的做法,看起來邊分治不光跑的慢還沒什麼不可替代性?
暴力寫掛
考慮那個式子有兩個不同樹上的 \(\operatorname\),不好處理,考慮怎麼換成乙個
由於 \(dis(x,y)=deep(x)+deep(y)-2deep(\operatorname(x,y))\),於是用 \(dis\) 代換:\(\dfrac(dis(x,y)+deep(x)+deep(y)-2deep'(\operatorname(x,y)))\),後面省略那個 \(\frac\)
可以先對第乙個樹進行點分治,設當前的分治中心是 \(u\),那麼式子就變成了 \(dis(u,x)+dis(u,y)+deep(x)+deep(y)-2deep'(\operatorname(x,y))\)
設 \(w(x)=dis(u,x)+deep(x)\),這個可以一遍 dfs 求出
那麼要求的東西最終就變成了 \(w(x)+w(y)-2deep'(\operatorname(x,y))\),這個還要求 \(x,y\) **於 \(u\) 的不同的子樹
可以把當前要分治的聯通塊拿出來,在第二課樹上建立虛樹,虛樹上 dp,設 \(f(u,0)\) 表示 \(u\) 子樹內 \(w(x)\) 最大的點,\(f(u,1)\) 表示次大、且滿足和最大點在第一棵樹中不屬於同一子樹 的點
轉移簡單,在 \(\operatorname\) 處更新答案即可
對於原本不在當前要分治的聯通塊,但是因為結構需要被加入虛樹的點,可以以他為 \(\operatorname\) 更新答案,但是不能用他的 \(w\) 初始化 \(f(u,0)\)
st 表求 \(\operatorname\) 加上基數排序實現線性建虛樹可以做到 \(o(n\log n)\)
#define n 400006
#define m 800006
#define log_n 20
struct graph
};int n;
graph t;
int st[log_n][n*2],lg2[n*2];
int dfscnt,dfn[n],id[n*2];
int deep[n];
long long sumt[n];
void dfst(int u,int fa=0)
}inline int getlca(int u,int v)
};inline void update(ans &u0,ans &u1,const ans &max,const ans &max2)
}int real[n],realid;//is vertex u really exist?
ans f[n][2];
long long ans;
void dp(int u,int fa=0);
if(real[u]==realid) f[u][0]=(ans);
for(int v,i=h.fir[u];i;i=h.nex[i])
}graph g;
long long sumg[n];
void pre(int u,int fa=0)
}int root,maxson[n],vis[n],size[n];
void dfs(int u,int color,int *a,int fa=0)
w[u]+=sumg[u];
}inline void calc(int u)
w[u]+=sumg[u];
build(a[0],a);
dp(1);
}void findroot(int u,int fa=0)
lib::chkmax(maxson[u],size[0]-size[u]);
if(maxson[u]size[u]?(tot-size[u]):size[v];
findroot(v);divide(root,size[v]); }}
int main()
通道
和上乙個一樣,還是想辦法通過列舉一些東西來減少變化的量,盡量讓變化的東西都在乙個樹上,尤其是減少式子中在多個樹上求 \(\operatorname\) 的情況
考慮在第乙個樹上點分治,分治中心是 \(u\),然後在第二個樹上列舉 \(p\) 表示這兩個點 \(x,y\) 在第二個樹上的 \(\operatorname\)
式子變成了:\(dis_1(x,u)+dis_1(y,u)+deep_2(x)+deep_2(y)-2deep_2(p)+dis_3(x,y)\),需要保證 \(x,y\) 在第
一、第二棵樹上都來自不同的子樹
對第二課建立虛樹,在第二課樹上來自的子樹不同很好保證,只需要注意一下啊更新答案、合併 dp 陣列的順序即可
發現如果要滿足在第一顆樹上來自 \(u\) 的不同子樹,那麼很難設計出這個 dp,考慮每次只合併 \(u\) 的兩個子樹
可以證明:若給樹分成兩類點,那麼分別求出兩端點都是同一類點的兩條直徑,會得到四個端點,那麼兩端點不是同一類點的直徑的端點,一定在這四個點中
據此,維護 \(f(u,0/1)\) 表示第二課樹的子樹 \(u\) 內的所有點,在第三課樹上組成的直徑(兩端點都是第
一、第二類點)最長是多少、端點是啥
更新答案的時候,就可以對於每個孩子 \(v\),把他們的端點稍微組合組合就行了
把 \(f(v,0/1)\) 合併到 \(f(u,0/1)\) 的時候,仍然應用上面的性質,就把 \(v\) 裡面的點算作一類點,剩下的算作另一類,拿出端點來組合一下
至此解決了每次合併 \(u\)(分治中心)的兩棵子樹的問題,發現邊分治的話因為是每次找中心邊,一共只有兩個子樹,已經做完了。但是想要乙個點分治的做法
我們把 \(u\) 的所有子樹按照大小排序,按照合併果子的方式來合併他們(每次合併兩個),最後再把所有子樹和 \(u\) 合併一次
這樣做如果每次合併的複雜度是 \(o(size_a+size_b)\) 的,那麼點分治的總複雜度仍然是 \(o(n\log n)\) 的
線性建虛樹,並精細實現合併果子的過程:先基數排序,然後用兩個佇列維護。可以做到 \(o(n\log n)\)
但懶得這麼寫了,寫的 \(o(n\log^2 n)\) 已經排進了洛谷最優解第一頁。。。感覺要是再取個 \(\log\) 就最優解了(
#define n 100006
#define m 200006
#define log_n 18
int lg2[n*2];
struct graph
inline void clear()
inline void read(int n)
inline node operator + (const node &o)const
};node f[n][2];
inline void update(int u,int v)
inline void build(int n,int *node,const graph &g)
}struct node);
} while(set.size()>=2));
} node[0]=0;node[++node[0]]=u;color[u]=0;
int a=(*set.begin()).u;
for(int v:ve[a]) node[++node[0]]=v,color[v]=1;
virtualt::work(node[0],node);
for(int i=g.fir[u];i;i=g.nex[i]) std::vector().swap(ve[g.to[i]]);
}void findroot(int u,int fa=0)
lib::chkmax(maxson[u],size[0]-size[u]);
if(maxson[u]size[u]?(tot-size[u]):size[v];
findroot(v);divide(root,size[v]); }}
inline void work(int n)
}//namespace divide
int main()
CTSC2018 暴力寫掛
題目 邊分治 虛樹 雙倍的快樂 這個柿子裡有兩個 lca 我們考慮魔改一下前面的 operatorname 為了方便邊分,我們考慮把 operatorname 去掉變換為樹上距離 經過一番魔改,這個柿子變成了 frac operatorname 至於第二棵樹上的 operatorname 我們只能考...
CTSC2018 暴力寫掛
題目鏈結 ctsc2018 暴力寫掛 做法 dep x dep y dep lca x,y dep lca x,y frac dep x dep y 2dep lca x,y dep x dep y 2dep lca x,y frac dis x,y dep x dep y 2dep lca x,y...
CTSC2018 暴力寫掛 邊分樹合併
ctsc2018 暴力寫掛 題面不錯 給定兩棵樹,兩點 距離 定義為 二者深度相加,減去兩棵樹上的lca的深度 深度指到根節點的距離 求最大的距離。解決多棵樹的問題就是降維了。經典的做法是邊分樹合併。邊分樹結構類似0 1 trie 就是把邊分樹對於每個點拆開路徑 合併兩棵邊分樹同時可以得到兩個邊分樹...