zoukankan      html  css  js  c++  java
  • 题解 P4244-[SHOI2008]仙人掌图

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

    解法

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

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

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

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

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

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

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

    [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;
    }
    
  • 相关阅读:
    C#获取视频文件播放长度
    ViewState跨页传值
    radio点击事件
    js屏蔽鼠标右键
    js获取url参数
    js页面跳转
    android 界面刷新功能
    android RadioButton单选按钮效果
    android TextView实现跑马灯效果(字体滚动)
    android 圆角效果
  • 原文地址:https://www.cnblogs.com/BlankAo/p/14244657.html
Copyright © 2011-2022 走看看