题解 P4244-[SHOI2008]仙人掌图
\(\Large\natural\) P4244 [SHOI2008]仙人掌图 \ 原题链接
解法
一开始看见仙人掌,按套路要建出圆方树,但后来发现其实是不用建的。
回想一下求普通树的直径时候的 \(\text{dp}\) 方法,可爱而无害。
设 \(o\) 为当前 \(\text{dfs}\) 到的点, \(v\) 为它的一个儿子结点,\(f_x\) 为点 \(x\) 到叶子结点的最长距离(这个定义全文适用),则有:
现在尝试把它扩展至毒瘤的仙人掌来。
我们发现,如果 \(o\) 与儿子 \(v\) 不共存与一个环,那么它们还是可以用上面的 \(\text{dp}\) 方式的。但是如果共存于一个环,那还是要特殊处理。
在 \(\text{tarjan}\) 的时候,如果 \(low_v>dfn_o\) 那么就表示 \(v\) 与 \(o\) 不在一个环上,就大胆放心地用上面的 \(\text{dp}\) 方式。
如果在一个环中,那么我们就要把这个环抽出来:记录一下每个点是从哪个点搜过来的,记为 \(fa\) 。\(\text{tarjan}\) 的末尾再遍历边一遍,如果找到一个点 \(x\),\(fa_x\ne o\) 且 \(dfn_v>dfn_o\) 那就说明我们找到了一个环的两个点。然后不断让 \(x\) 往上跳,即不断令 \(x=fa_x\) 就可以找到整个环。
其原理是:从 \(o\) 开始 \(\text{tarjan}\) ,如果有环,会沿着环走,最后到达 \(o\) 的儿子。而且每次 \(x=fa_x\) 就相当于沿着搜索轨迹倒退,最后终能复原整个环。
然后找到了这个环怎么办呢?画画仙人掌,发现底下的东西都已不再重要,只有 \(f_o\) 值得更新了。所以现在的任务是:在这个环中更新 \(ans\) 。并更新 \(f_o\)。
更新 \(ans\),首先按照环形套路,断环为链,将环复制一倍。找到两个距离不超过半环的点。(如果超过半环了就会反着来,比如环 1-2-3-4 首尾相接,断环成链 1-2-3-4-1-2-3-4 ,1-4 不会统计,因为它路径长度为3,而 4-1会统计)这样处理,我们就可以有一个显然的更新。
设原来环的长度为 \(len\) ,存环的数组为 \(a\) ,现在枚举到了下标 \(i\) 和 \(j\) ,使得 \(i>j\) 且 \(i-j<\frac{len}2\) ,则两个点是\(a_i,a_j\)。
这个可以用单调队列优化,设队尾的点是 \(x\) ,在环中下标为 \(y\) ,如果 \(f_x-y<f_{ai}-i\) 就队尾左移。
更新 \(f_o\) 简单。设 \(x\) 为环上一个点(不为 \(o\)),\(l\)为 \(x\) 到 \(o\) 的距离。则
注意更新的顺序。
然后就做完啦~
代码
开了 O2 后,居然是目前的最优解诶
码风毒瘤见谅
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
using namespace std;
const int n7=50123,l7=100123,m7=500123;
struct dino{int to,nxt;}e[m7];
int n,m,ecnt,t,fst[n7],f[n7],ans;
int dfn[n7],low[n7],fa[n7],lin[l7];
struct MoquE{
int head,tail,que[l7];
void clear(){head=1,tail=0;que[1]=0;}
void updat(int id,int cnt){
while(head<=tail && id-que[head]>cnt/2)head++;
ans=max(ans,f[ lin[id] ]+f[ lin[ que[head] ] ]+id-que[head]);
while(head<=tail && f[ lin[ que[tail] ] ]-que[tail]<f[ lin[id] ]-id)tail--;
tail++,que[tail]=id;
}
}qued;
int rd(){
int shu=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))shu=(shu<<1)+(shu<<3)+(ch^48),ch=getchar();
return shu;
}
void edge(int sta,int edn){
ecnt++;
e[ecnt]=(dino){edn,fst[sta]};
fst[sta]=ecnt;
}
void calc(int o1,int o2){
int cnt=0;
for(int o=o2;o!=o1;o=fa[o]){
cnt++,lin[cnt]=o;
}
cnt++,lin[cnt]=o1;
rep(i,1,cnt)lin[i+cnt]=lin[i];
qued.clear();
rep(i,1,cnt*2)qued.updat(i,cnt);
rep(i,1,cnt-1)f[o1]=max(f[o1],f[ lin[i] ]+min(i,cnt-i));
}
void tarjan(int o){
t++,dfn[o]=low[o]=t;
mar(o){
if(v==fa[o])continue;
if(!dfn[v]){
fa[v]=o,tarjan(v);
low[o]=min(low[o],low[v]);
}
low[o]=min(low[o],dfn[v]);
if(low[v]>dfn[o]){
ans=max(ans,f[o]+f[v]+1);
f[o]=max(f[o],f[v]+1);
}
}
mar(o){
if(fa[v]!=o&&dfn[v]>dfn[o])calc(o,v);
}
}
int main(){
n=rd(),m=rd();
rep(i,1,m){
int num=rd()-1,o1,o2=rd();
while(num--)o1=rd(),edge(o1,o2),edge(o2,o1),o2=o1;
}
tarjan(1);
printf("%d",ans);
return 0;
}