Warning: session_start(): open(/tmp/sess_be018642578e0cbddc2f940fd87bc86f, O_RDWR) failed: No space left on device (28) in /data/wiki/inc/init.php on line 239

Warning: session_start(): Failed to read session data: files (path: ) in /data/wiki/inc/init.php on line 239

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/auth.php on line 430

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/actions.php on line 38

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/lib/tpl/dokuwiki/main.php on line 12
2020-2021:teams:legal_string:jxm2001:基环树 [CVBB ACM Team]

用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:基环树

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:jxm2001:基环树 [2021/07/16 11:00]
jxm2001
2020-2021:teams:legal_string:jxm2001:基环树 [2021/07/17 09:26] (当前版本)
jxm2001 [例题四]
行 32: 行 32:
  int to,id,next;  int to,id,next;
 }edge[MAXN<<​1];​ }edge[MAXN<<​1];​
-bool vis[MAXN<<1];+bool vis[MAXN];
 int head[MAXN],​edge_cnt;​ int head[MAXN],​edge_cnt;​
 void Insert(int u,int v,int id){ void Insert(int u,int v,int id){
行 151: 行 151:
  }  }
  enter(ans);​  enter(ans);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +
 +==== 例题三 ====
 +
 +[[https://​www.luogu.com.cn/​problem/​P4381|洛谷p4381]]
 +
 +=== 题意 ===
 +
 +题目大意就省略了,大概就是给定基环树森林,求所有基环树的最长路径之和。(注意最长路径 $\neq$ 直径)
 +
 +=== 题解 ===
 +
 +考虑每棵基环树的情况,首先找到环,先不考虑环上的边,求出环上每个点的子树的直径的最大值,这是可能的答案之一。
 +
 +另外求出环上每个点到它子树的叶子节点的最远距离 $d$。设环长为 $s$,任取环上的点对 $(u,​v)$,于是另一种答案为 ​
 +
 +$$
 +\max(d(u)+d(v)+\text{dis}(u,​v),​d(u)+d(v)+s-\text{dis}(u,​v))
 +$$
 +
 +考虑规定一个环上的起始点,于是 $\text{dis}(u,​v)=\text{pre}(u)-\text{pre}(v)$,维护两个前缀 $\text{max}$ 即可 $O(n)$ 统计所有点对贡献。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=1e6+5;
 +const LL inf=1e16;
 +struct Edge{
 + int to,​w,​id,​next;​
 +}edge[MAXN<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Insert(int u,int v,int w,int id){
 + edge[++edge_cnt]=Edge{v,​w,​id,​head[u]};​
 + head[u]=edge_cnt;​
 +}
 +bool edge_vis[MAXN],​node_vis[MAXN],​node_cyc[MAXN];​
 +LL d[MAXN],​cyc_len;​
 +vector<​int>​ cyc;
 +bool dfs1(int u,LL dep){
 + if(node_vis[u]){
 + cyc_len=dep-d[u];​
 + cyc.push_back(u);​
 + return node_cyc[u]=true;​
 + }
 + d[u]=dep;
 + node_vis[u]=true;​
 + bool flag=false;
 + for(int i=head[u];​i;​i=edge[i].next){
 + if(edge_vis[edge[i].id])continue;​
 + edge_vis[edge[i].id]=true;​
 + int v=edge[i].to;​
 + if(dfs1(v,​dep+edge[i].w)&&​!node_cyc[u]){
 + cyc.push_back(u);​
 + node_cyc[u]=true;​
 + flag=true;​
 + }
 + }
 + return flag;
 +}
 +LL dfs2(int u,int fa){
 + LL ans=0;
 + d[u]=0;
 + for(int i=head[u];​i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(v==fa||node_cyc[v])continue;​
 + ans=max(ans,​dfs2(v,​u));​
 + ans=max(ans,​d[u]+d[v]+edge[i].w);​
 + d[u]=max(d[u],​d[v]+edge[i].w);​
 + }
 + return ans;
 +}
 +LL pre[MAXN];
 +LL solve(int u){
 + cyc.clear();​
 + dfs1(u,0);
 + for(int i=1,​j=cyc.size()-1;​i<​j;​i++,​j--)swap(cyc[i],​cyc[j]);​
 + _for(i,​1,​cyc.size())
 + pre[cyc[i]]=d[cyc[i]]-d[cyc[0]];​
 + LL ans=0,​max1=-inf,​max2=-inf;​
 + _for(i,​0,​cyc.size()){
 + ans=max(ans,​dfs2(cyc[i],​0));​
 + ans=max(ans,​d[cyc[i]]+pre[cyc[i]]+max1);​
 + ans=max(ans,​d[cyc[i]]+cyc_len-pre[cyc[i]]+max2);​
 + max1=max(max1,​d[cyc[i]]-pre[cyc[i]]);​
 + max2=max(max2,​d[cyc[i]]+pre[cyc[i]]);​
 + }
 + return ans;
 +}
 +int main()
 +{
 + int n=read_int();​
 + _rep(u,​1,​n){
 + int v=read_int(),​w=read_int();​
 + Insert(u,​v,​w,​u);​
 + Insert(v,​u,​w,​u);​
 + }
 + LL ans=0;
 + _rep(u,​1,​n){
 + if(!node_vis[u])
 + ans+=solve(u);​
 + }
 + enter(ans);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +
 +==== 例题四 ====
 +
 +[[https://​www.luogu.com.cn/​problem/​P1399|洛谷p1399]]
 +
 +=== 题意 ===
 +
 +给定一个基环树,要求在图上找到一个点,使得该点到其他点的最大距离最小。(该点可以在边上的某个位置)
 +
 +=== 题解 ===
 +
 +显然答案是基环树的直径除以 $2$,至于基环树的直径,首先子树贡献同基环树的最长路径,但环上点对 $(u,v)$ 的贡献改为
 +
 +$$
 +\min(d(u)+d(v)+\text{dis}(u,​v),​d(u)+d(v)+s-\text{dis}(u,​v))
 +$$
 +
 +考虑依次断开一条边,得到链后计算 $\max(d(u)+d(v)+\text{dis}(u,​v))$ 得到生成树的直径,则所有断边情况中的最小值就是基环树的直径。
 +
 +简短证明一下上述结论:首先任意断开一条环上的边可以得到一棵生成树,显然生成树的直径不小于基环树的直径。
 +
 +事实上,取基环树直径的中点 $rt$,做 $rt$ 的最短路生成树,则最短路生成树的直径等于基环树的直径。证毕。
 +
 +但这样时间复杂度是 $O(n^2)$,考虑优化。记环上的点分别为 $u_1,​u_2\cdots u_m$,考虑先断开 $(u_1,u_m)$ 边,求出前缀
 +
 +$$
 +\text{pre1}(i)=\max_{1\le j\le i}(d(u_j)+\text{dis}(u_j,​u_1))\\
 +\text{pre2}(i)=\max_{1\le j,k\le i}(d(u_j)+d(u_k)+\text{dis}(u_j,​u_k))
 +$$
 +
 +类似定义后缀 $\text{suf1}(i),​\text{suf2}(i)$,于是断开 $(u_1,u_m)$ 边的贡献为 $\text{pre2}(k)$。
 +
 +接下来考虑断开 $(u_i,​u_{i-1})$ 的边,分 $(u\lt i,v\ge i),(u,v\lt i),(u,v\ge i)$ 三种情况讨论,此时贡献为
 +
 +$$
 +\max\left(\text{pre1}(i-1)+\text{suf1}(i)+\text{dis}(u_{i-1},​u_i),​\max(\text{pre2}(i-1),​\text{suf2}(i))\right)
 +$$
 +
 +总时间复杂度 $O(n)$。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=1e5+5;
 +const LL inf=1e16;
 +struct Edge{
 + int to,​w,​id,​next;​
 +}edge[MAXN<<​1];​
 +int head[MAXN],​edge_cnt;​
 +void Insert(int u,int v,int w,int id){
 + edge[++edge_cnt]=Edge{v,​w,​id,​head[u]};​
 + head[u]=edge_cnt;​
 +}
 +bool edge_vis[MAXN],​node_vis[MAXN],​node_cyc[MAXN];​
 +LL d[MAXN],​cyc_len;​
 +vector<​int>​ cyc;
 +bool dfs1(int u,LL dep){
 + if(node_vis[u]){
 + cyc_len=dep-d[u];​
 + cyc.push_back(u);​
 + return node_cyc[u]=true;​
 + }
 + d[u]=dep;
 + node_vis[u]=true;​
 + bool flag=false;
 + for(int i=head[u];​i;​i=edge[i].next){
 + if(edge_vis[edge[i].id])continue;​
 + edge_vis[edge[i].id]=true;​
 + int v=edge[i].to;​
 + if(dfs1(v,​dep+edge[i].w)&&​!node_cyc[u]){
 + cyc.push_back(u);​
 + node_cyc[u]=true;​
 + flag=true;​
 + }
 + }
 + return flag;
 +}
 +LL dfs2(int u,int fa){
 + LL ans=0;
 + d[u]=0;
 + for(int i=head[u];​i;​i=edge[i].next){
 + int v=edge[i].to;​
 + if(v==fa||node_cyc[v])continue;​
 + ans=max(ans,​dfs2(v,​u));​
 + ans=max(ans,​d[u]+d[v]+edge[i].w);​
 + d[u]=max(d[u],​d[v]+edge[i].w);​
 + }
 + return ans;
 +}
 +LL pre[MAXN],​suf[MAXN],​pre1[MAXN],​pre2[MAXN],​suf1[MAXN],​suf2[MAXN];​
 +LL solve(int u){
 + cyc.clear();​
 + dfs1(u,0);
 + for(int i=1,​j=cyc.size()-1;​i<​j;​i++,​j--)swap(cyc[i],​cyc[j]);​
 + _for(i,​0,​cyc.size()){
 + pre[cyc[i]]=d[cyc[i]]-d[cyc[0]];​
 + suf[cyc[i]]=d[cyc[cyc.size()-1]]-d[cyc[i]];​
 + }
 + LL w0=cyc_len-d[cyc[cyc.size()-1]];​
 + LL ans1=0,​pre_t=-inf,​suf_t=-inf;​
 + _for(i,​0,​cyc.size())ans1=max(ans1,​dfs2(cyc[i],​0));​
 + _for(i,​0,​cyc.size()){
 + pre1[cyc[i]]=d[cyc[i]]+pre[cyc[i]];​
 + pre2[cyc[i]]=d[cyc[i]]+pre[cyc[i]]+pre_t;​
 + pre_t=max(pre_t,​d[cyc[i]]-pre[cyc[i]]);​
 + }
 + _for(i,​1,​cyc.size()){
 + pre1[cyc[i]]=max(pre1[cyc[i]],​pre1[cyc[i-1]]);​
 + pre2[cyc[i]]=max(pre2[cyc[i]],​pre2[cyc[i-1]]);​
 + }
 + for(int i=cyc.size()-1;​i>​=0;​i--){
 + suf1[cyc[i]]=d[cyc[i]]+suf[cyc[i]];​
 + suf2[cyc[i]]=d[cyc[i]]+suf[cyc[i]]+suf_t;​
 + suf_t=max(suf_t,​d[cyc[i]]-suf[cyc[i]]);​
 + }
 + for(int i=cyc.size()-2;​i>​=0;​i--){
 + suf1[cyc[i]]=max(suf1[cyc[i]],​suf1[cyc[i+1]]);​
 + suf2[cyc[i]]=max(suf2[cyc[i]],​suf2[cyc[i+1]]);​
 + }
 + LL ans2=pre2[cyc[cyc.size()-1]];​
 + _for(i,​1,​cyc.size())
 + ans2=min(ans2,​max(pre1[cyc[i-1]]+w0+suf1[cyc[i]],​max(pre2[cyc[i-1]],​suf2[cyc[i]])));​
 + return max(ans1,​ans2);​
 +}
 +int main()
 +{
 + int n=read_int();​
 + _rep(i,​1,​n){
 + int u=read_int(),​v=read_int(),​w=read_int();​
 + Insert(u,​v,​w,​i);​
 + Insert(v,​u,​w,​i);​
 + }
 + LL ans=0;
 + _rep(u,​1,​n){
 + if(!node_vis[u])
 + ans+=solve(u);​
 + }
 + if(ans%2==0)
 + printf("​%lld.0",​ans/​2);​
 + else
 + printf("​%lld.5",​ans/​2);​
  return 0;  return 0;
 } }
 </​code>​ </​code>​
 </​hidden>​ </​hidden>​
2020-2021/teams/legal_string/jxm2001/基环树.1626404413.txt.gz · 最后更改: 2021/07/16 11:00 由 jxm2001