WC2018 通道 與 CTSC2018 暴力寫掛

2022-10-03 22:57:21 字數 4039 閱讀 8271

兩個都給出點分治的做法,看起來邊分治不光跑的慢還沒什麼不可替代性?

暴力寫掛

考慮那個式子有兩個不同樹上的 \(\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 就是把邊分樹對於每個點拆開路徑 合併兩棵邊分樹同時可以得到兩個邊分樹...