zoukankan      html  css  js  c++  java
  • 【BZOJ2870】最长道路(边分治入门)

    点此看题面

    • 给定一棵树,求一条树上路径使得点数$ imes $最小点权最大。
    • (nle5 imes10^4)

    边分治入门

    首先说说边分治有什么好处,因为一条边只会连向两个点,所以总共只有两个子树,两子树答案的合并就非常方便,而不需要考虑多子树合并的问题。

    类比点分治,点分治每次选择重心进行分治,那么边分治每次肯定是找到一条边满足两个点数较大值尽可能小,处理经过这条边的路径,然后进行分治。

    但是,实际上这种做法会被菊花图卡爆。

    所以我们需要优化,把原图进行“三度化”。

    具体地,对于一个点,对于它的每个叶节点,我们新建一个辅助节点。还是看图比较直观:

    辅助点的点权显然直接继承父节点,父节点与辅助点、辅助点与辅助点之间的边长设为(0),辅助点与子节点之间的边长设为(1)

    这样一转化就可以肆无忌惮毫无顾虑地边分治了。

    对于此题

    一道边分治裸题。

    直接对于当前的分治边,求出两个子树内每种深度的链,链上最小值的最大值。

    求解答案就枚举一条链的长度,双指针维护另一条链在不影响最小值前提下的最大值即可。

    代码:(O(nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 50000
    #define LL long long
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,a[2*N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	char oc,FI[FS],*FA=FI,*FB=FI;
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }using namespace FastIO;
    namespace EdgeSolver//边分治
    {
    	#define E(x) ((((x)-1)^1)+1)//反向边
    	#define Tadd(x,y,z) (Te[++Tee].nxt=Tlnk[x],Te[Tlnk[x]=Tee].to=y,Te[Tee].c=z)
    	LL ans;int cnt,Tee,Tlnk[2*N+5];struct Tedge {int to,nxt,c,g;}Te[4*N+5];
    	I void ReBuild(CI x,CI lst=0)//重构
    	{
    		for(RI i=lnk[x],y=x;i;i=e[i].nxt) e[i].to^lst&&(++cnt,a[n+cnt]=a[x],//建辅助点
    			Tadd(y,n+cnt,0),Tadd(n+cnt,y,0),Tadd(n+cnt,e[i].to,1),Tadd(e[i].to,n+cnt,1),y=n+cnt);//连辅助边
    		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(ReBuild(e[i].to,x),0);
    	}
    	int ky,Sz[2*N+5],Mx[4*N+5];I void GetKey(CI x,CI pre,RI s)//找出分治边
    	{
    		Sz[x]=1;for(RI i=Tlnk[x];i;i=Te[i].nxt) i^E(pre)&&!Te[i].g&&(GetKey(Te[i].to,i,s),Sz[x]+=Sz[Te[i].to]);
    		pre&&(Mx[pre]=max(Sz[x],s-Sz[x]))<Mx[ky]&&(ky=pre);//使两边点数较大值尽可能小
    	}
    	int v1[2*N+5],v2[2*N+5],p1[2*N+5],p2[2*N+5];I void dfs(int* v,int* p,CI ti,CI x,CI w,CI lst=0,CI d=0)//dfs遍历子树
    	{
    		p[d]^ti?(v[d]=w,p[d]=ti):v[d]=max(v[d],w);for(RI i=Tlnk[x];i;i=Te[i].nxt)//某个深度在某条边第一次访问时先初始化
    			Te[i].to^lst&&!Te[i].g&&(dfs(v,p,ti,Te[i].to,min(w,a[Te[i].to]),x,d+Te[i].c),0);
    	}
    	I void Work(RI k,RI s)//边分治
    	{
    		RI x=Te[k].to,y=Te[E(k)].to;Te[k].g=Te[E(k)].g=1,dfs(v1,p1,k,x,a[x]),dfs(v2,p2,k,y,a[y]);//dfs两个子树
    		for(RI i=0,j=-1;p1[i]==k;++i) {W(p2[j+1]==k&&v2[j+1]>=v1[i]) ++j;~j&&(ans=max(ans,1LL*(i+j+Te[k].c+1)*v1[i]));}//枚举一条链长,双指针维护另一条链
    		for(RI i=-1,j=0;p2[j]==k;++j) {W(p1[i+1]==k&&v1[i+1]>=v2[j]) ++i;~i&&(ans=max(ans,1LL*(i+j+Te[k].c+1)*v2[j]));}//反过来再做一遍
    		ky=0,GetKey(x,0,Sz[x]),ky&&(Work(ky,Sz[x]),0),ky=0,GetKey(y,0,s-Sz[x]),ky&&(Work(ky,s-Sz[x]),0);//继续边分治
    	}
    	I void Solve() {Mx[ky=0]=1e9,GetKey(1,0,n),Work(ky,n),printf("%lld
    ",ans);}//开始边分治
    }
    int main()
    {
    	RI i,x,y;for(read(n),i=1;i<=n;++i) read(a[i]);for(i=1;i^n;++i) read(x,y),add(x,y),add(y,x);
    	return EdgeSolver::ReBuild(1),EdgeSolver::Solve(),0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    markdown常用语法
    利用 js-xlsx 实现选择 Excel 文件在页面显示
    HTML中meta标签
    wxpy模块
    Python基础(3)
    Python基础(2)
    Python基础(1)
    Python之递归锁与互斥锁
    Python进程与线程
    Docker
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2870.html
Copyright © 2011-2022 走看看