[BZOJ2870][边分治]最长道路tree

BZOJ2870

点分治处理不了最小值

边分治,和点分治类似,就是选择一条边,使其两端的子树最大的最小
然后一样容易被卡(菊花图)
所以把树建成一棵二叉树,如果一个点的儿子大于2个就建两个新点管理它的儿子,如果还多于两个就继续建(和线段树一样)这样保证了复杂度
那么考虑这道题:最小值用边分治显然就很好处理了,然后我们把新建的边的权值赋为0,原边的权值为1,那么两点之间的点数就是边权和+1
那么我们对于每次分治,可以把左右的拿出来分别排序,然后在另一边找到最小值大于它的最长的一条更新答案

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=2e5+5,INF=0x3f3f3f3f;
int vis[N<<1],head[N],c[N],nxt[N<<1],tot=0;
inline void add(int x,int y,int z){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;c[tot]=z;}
vector<int>e[N];
int n,kn,rt,maxn;
int val[N];
void dfs(int v,int fa){
	for(int i=head[v];i;i=nxt[i])
		if(vis[i]!=fa) e[v].push_back(vis[i]),dfs(vis[i],v);
}
inline void rebuild(){
	tot=1;memset(head,0,sizeof(head));
	for(int i=1;i<=n;i++){
		int siz=e[i].size();
		if(siz<=2) for(int j=0;j<siz;j++) add(i,e[i][j],(e[i][j]<=kn)),add(e[i][j],i,(e[i][j]<=kn));
		else{
			int o1=++n,o2=++n;
			val[o1]=val[o2]=val[i];
			add(i,o2,0);add(o2,i,0);
			add(i,o1,0);add(o1,i,0);
			for(int j=0;j<siz;j++){
				if(j&1) e[o2].push_back(e[i][j]);
				else e[o1].push_back(e[i][j]);
			}
		}
	}
}
int siz[N],pt[N];
void getroot(int v,int fa,int S){
	siz[v]=1;
	for(int i=head[v];i;i=nxt[i]){
		if(pt[i>>1] || vis[i]==fa) continue;
		getroot(vis[i],v,S);
		siz[v]+=siz[vis[i]];
		int res=max(siz[vis[i]],S-siz[vis[i]]);
		if(res<maxn) maxn=res,rt=i;
	}
}
struct node{int v,len;}t[2][N];
inline bool cmp(node a,node b){return a.v>b.v;}
int cnt[2];
void solve(int v,int fa,int len,int V,int num){
	V=min(V,val[v]);t[num][++cnt[num]]=(node){V,len};
	for(int i=head[v];i;i=nxt[i]){
		if(pt[i>>1] || vis[i]==fa) continue;
		solve(vis[i],v,len+c[i],V,num);
	}
}
ll ans=0;
void work(int v,int S){
	maxn=INF;getroot(v,0,S);
	if(maxn==INF) return;
	int now=rt;pt[now>>1]=1;
	cnt[0]=cnt[1]=0;
	solve(vis[now],0,0,INF,0);
	solve(vis[now^1],0,0,INF,1);
	sort(t[0]+1,t[0]+cnt[0]+1,cmp);
	sort(t[1]+1,t[1]+cnt[1]+1,cmp);
	for(int i=1,j=1,maxx=0;i<=cnt[0];i++){
		while(j<=cnt[1] && t[1][j].v>=t[0][i].v) maxx=max(maxx,t[1][j].len),++j;
		if(j!=1) ans=max(ans,1ll*t[0][i].v*(maxx+t[0][i].len+c[now]+1));
	}
	for(int i=1,j=1,maxx=0;i<=cnt[1];i++){
		while(j<=cnt[0] && t[0][j].v>=t[1][i].v) maxx=max(maxx,t[0][j].len),++j;
		if(j!=1) ans=max(ans,1ll*t[1][i].v*(maxx+t[1][i].len+c[now]+1));
	}
	int kk=siz[vis[now]];
	work(vis[now],kk);
	work(vis[now^1],S-kk);
}
int main(){
    n=kn=read();
    for(int i=1;i<=n;++i) val[i]=read();
    for(int x,y,i=1;i<n;++i) x=read(),y=read(),add(x,y,1),add(y,x,1);
    dfs(1,0),rebuild(),work(1,n);
    cout<<ans<<"\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值