(Large atural) P4244 [SHOI2008]仙人掌图 原题链接
解法
一开始看见仙人掌,按套路要建出圆方树,但后来发现其实是不用建的。
回想一下求普通树的直径时候的 ( ext{dp}) 方法,可爱而无害。
设 (o) 为当前 ( ext{dfs}) 到的点, (v) 为它的一个儿子结点,(f_x) 为点 (x) 到叶子结点的最长距离(这个定义全文适用),则有:
现在尝试把它扩展至毒瘤的仙人掌来。
我们发现,如果 (o) 与儿子 (v) 不共存与一个环,那么它们还是可以用上面的 ( ext{dp}) 方式的。但是如果共存于一个环,那还是要特殊处理。
在 ( ext{tarjan}) 的时候,如果 (low_v>dfn_o) 那么就表示 (v) 与 (o) 不在一个环上,就大胆放心地用上面的 ( ext{dp}) 方式。
如果在一个环中,那么我们就要把这个环抽出来:记录一下每个点是从哪个点搜过来的,记为 (fa) 。( ext{tarjan}) 的末尾再遍历边一遍,如果找到一个点 (x),(fa_x e o) 且 (dfn_v>dfn_o) 那就说明我们找到了一个环的两个点。然后不断让 (x) 往上跳,即不断令 (x=fa_x) 就可以找到整个环。
其原理是:从 (o) 开始 ( ext{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;
}