题解 P4244-[SHOI2008]仙人掌图

\(\Large\natural\) P4244 [SHOI2008]仙人掌图 \ 原题链接

解法

一开始看见仙人掌,按套路要建出圆方树,但后来发现其实是不用建的。

回想一下求普通树的直径时候的 \(\text{dp}\) 方法,可爱而无害。

\(o\) 为当前 \(\text{dfs}\) 到的点, \(v\) 为它的一个儿子结点,\(f_x\) 为点 \(x\) 到叶子结点的最长距离(这个定义全文适用),则有:

\[f_o=max(f_o,f_v+1) \]

\[ans=max(ans,f_o+f_v+1) \]

现在尝试把它扩展至毒瘤的仙人掌来。

我们发现,如果 \(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\)

\[ans=max(ans,i-j+f_{ai}+f_{aj}) \]

这个可以用单调队列优化,设队尾的点是 \(x\) ,在环中下标为 \(y\) ,如果 \(f_x-y<f_{ai}-i\) 就队尾左移。

更新 \(f_o\) 简单。设 \(x\) 为环上一个点(不为 \(o\)),\(l\)\(x\)\(o\) 的距离。则

\[f_o=max(f_o,f_x+l) \]

注意更新的顺序。

然后就做完啦~

代码

开了 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;
}
posted @ 2021-01-07 09:18  BlankAo  阅读(112)  评论(0编辑  收藏  举报