CodeChef:Maximum Tree Path

题目传送门
题目大意:有一棵树,每个点有点值,每条边有边权,设 g c d ( u , v ) gcd(u,v) gcd(u,v)表示 u u u v v v上所有点的点值的 g c d gcd gcd,设 d i s ( u , v ) dis(u,v) dis(u,v) u u u v v v上所有边的边权和,设 m i n ( u , v ) min(u,v) min(u,v)表示 u u u v v v上所有点的点值最小值,求最大的 g c d ( x , y ) ∗ m i n ( x , y ) ∗ d i s ( x , y ) gcd(x,y)*min(x,y)*dis(x,y) gcd(x,y)min(x,y)dis(x,y)

这题一看就很不可做,确实有点不可做,甚至让我连标题都不知道该怎么取。干脆就直接把题目出处和名称放上去,后面的算法标签实在是标不来。

废话少说,我们观察了数据范围之后,有了一种巧妙的做法,我们每次枚举 g c d gcd gcd的值,然后再从大到小加入每一条权值为为当前枚举的 g c d gcd gcd倍数的边,边加入边维护当前树的直径(因为答案是 g c d ∗ m i n ∗ d i s gcd*min*dis gcdmindis g c d gcd gcd已经枚举, m i n min min就是当前边的权值,而 d i s dis dis要最长,显然当前树的直径最长),这样统计出的答案就会小于等于最终的答案(因为可能所有边的权值都是当前枚举的 g c d gcd gcd的倍数,那么真正的 g c d gcd gcd就会变大),但随着 g c d gcd gcd从小到大枚举,最终即可获得全部的答案。
可以证明,这样的复杂度是可行的(O(跑得过),因为复杂度我也不太会证,但是感性理解就是gcd的取值实际上并不多)
所以剩下的问题就是维护树的直径了,这里有一个重要的结论,就是合并两棵树 X 、 Y X、Y XY时,如果 X X X的直径两端点是 a , b a,b a,b Y Y Y的直径两端点是 c , d c,d c,d,那么合并出来的树的直径的两端点一定是 a , b , c , d a,b,c,d a,b,c,d四个点中的两个。所以我们枚举每种可能的情况,算出 d i s dis dis值最大的那种情况,那种情况的两个点就是新的直径的两个端点。
#include<bits/stdc++.h>
#define MAXN 200005
#define ll long long
using namespace std;
ll read(){
	char c;ll x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
	if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
	x=x*10+c-'0';return x*y;
}
void print(ll x){
	if(x/10) print(x/10);
	putchar(x%10+'0');
}
ll T,n,mx,cnt,ans,tot,cur,res;
ll u[MAXN],v[MAXN],c[MAXN],dep[MAXN],pos[MAXN],fa[MAXN],a[MAXN],l[MAXN];
ll head[MAXN<<1],nxt[MAXN<<1],go[MAXN<<1],s[MAXN][20],t[MAXN][20],A[MAXN],B[MAXN],lst[MAXN];
vector<ll> g[MAXN];
struct node{
	ll to,val;
}L[MAXN<<1];
void add(ll x,ll y,ll c){
	L[cnt]=(node){y,c};
	nxt[cnt]=head[x];head[x]=cnt;cnt++;
	L[cnt]=(node){x,c};
	nxt[cnt]=head[y];head[y]=cnt;cnt++;
}
ll gcd(ll x,ll y){
	return y?gcd(y,x%y):x;
}
void getgcd(ll x,ll y){
	for(ll i=1;i*i<=x;i++)
	  if(x%i==0){
	  	g[i].push_back(y);
	  	if(x/i!=i) g[x/i].push_back(y);
	  }
}
ll find(ll x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
void unionn(ll &x,ll &y){
	x=find(x);y=find(y);fa[y]=x;
}
void dfs(ll x,ll fa){
	c[++tot]=dep[x];pos[x]=tot;
	for(ll i=head[x];i!=-1;i=nxt[i]){
		ll to=L[i].to;
		if(to==fa) continue;
		dep[to]=dep[x]+L[i].val;dfs(to,x);c[++tot]=dep[x];
	}
}
void pre(){
	tot=0;dfs(1,0);register int i,j;
	for(i=1;i<=tot;i++) s[i][0]=t[i][0]=c[i];
	for(j=1;j<=17;j++)
	 for(i=1;i<=tot;i++){
	 	if(i+(1<<j)-1<=tot) s[i][j]=min(s[i][j-1],s[i+(1<<j-1)][j-1]);
	 	if(i-(1<<j)>=0) t[i][j]=min(t[i][j-1],t[i-(1<<j-1)][j-1]);
	 }
}
ll rmq(ll x,ll y){   //这个实际上是处理从到LCA的路径长度,注意上面的c数组的构建,这个操作很风骚
	x=pos[x];y=pos[y];if(x>y) swap(x,y);
	ll k=(ll)log2(y-x+1);
	return min(s[x][k],t[y][k]);
}
bool cmp(ll x,ll y){
	return a[u[x]]>a[u[y]];
}
void wr(ll x,ll y){     //维护直径
	if(lst[x]!=cur) lst[x]=cur,A[x]=B[x]=fa[x]=x,l[x]=0;
	if(lst[y]!=cur) lst[y]=cur,A[y]=B[y]=fa[y]=y,l[y]=0;
	unionn(x,y);ll a1=A[x],b1=B[x];
	if(l[y]>l[x]) l[x]=l[y],a1=A[y],b1=B[y];
	ll num=dep[A[x]]+dep[A[y]]-2*rmq(A[x],A[y]);
	if(num>l[x]){l[x]=num;a1=A[x],b1=A[y];}	
	num=dep[A[x]]+dep[B[y]]-2*rmq(A[x],B[y]);
	if(num>l[x]){l[x]=num;a1=A[x],b1=B[y];}
	num=dep[B[x]]+dep[A[y]]-2*rmq(B[x],A[y]);
	if(num>l[x]){l[x]=num;a1=B[x],b1=A[y];}
	num=dep[B[x]]+dep[B[y]]-2*rmq(B[x],B[y]);
	if(num>l[x]){l[x]=num;a1=B[x],b1=B[y];}
	A[x]=a1;B[x]=b1;res=max(res,l[x]);
}
void solve(ll x){
	sort(g[x].begin(),g[x].end(),cmp);
	cur++;res=0;
	for(ll i=0;i<g[x].size();i++){
		ll p=g[x][i];
		wr(u[p],v[p]);
		ans=max(ans,res*x*a[u[p]]);
	}
}
int main()
{
	T=read();register int i;
	while(T--){
		n=read();ans=0;cnt=0;mx=0;
		memset(head,-1,sizeof(head));
		for(i=1;i<=n;i++) a[i]=read(),mx=max(mx,a[i]);
		for(i=1;i<=mx;i++) g[i].clear();
		for(i=1;i<n;i++){
			ll x=read(),y=read(),c=read();
			add(x,y,c);u[i]=x;v[i]=y;
			if(a[u[i]]>a[v[i]]) swap(u[i],v[i]);
			getgcd(gcd(a[u[i]],a[v[i]]),i);
		}
		pre();
		if(T==34){
			int ubt=0;
		}
		for(i=1;i<=mx;i++) 
		  solve(i);
		print(ans);puts("");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值