首页 > 技术文章 > 【HDU7024】Penguin Love Tour

Cry-For-theMoon 2021-08-07 17:31 原文

传送门

一道实际上很清新的题,结果我们三个人赛场上看都没看这道题。

第一眼看到题的时候没思路

题意:

给定大小为 \(n\) 的一棵树,带点权与边权。设第 \(i\) 个点的点权为 \(p_i\),对于每个点 \(u\),你可以选择一条与其相连的边,将其边权 \(w\) 变成 \(\max\{0,w-p_u\}\),一条边可以被它的两个端点都操作一次。

求操作结束后,能得到的最小的树的直径。

Data range:\(n \le 10^5\)

分析:

树上路径题,又是树的直径,所以不太可能是点分治或者直接dsu on tree之类的数据结构。考虑贪心或者dp,然后发现这题的直径是一个全局的东西,贪心和dp都不太好做。

然后发现这个最小直径是满足单调性的所以我们先二分一波。考虑检查树的直径能否小于等于 \(lim\)

发现贪心贪不出来什么东西,所以考虑 dp。类似树的直径的 \(dp\) 方法,我们 \(f(u)\) 里应该记录的是 \(u\) 下去的最长链,但是计算的时候,必须保证 \(u\) 下去的最长链和次长链相加不超过 \(lim\)

但是还不够,发现考虑一条边 \((u,v)\) 的时候,儿子 \(v\) 可以修改这条边,也可以不修改。所以我们多设一维:\(f(u,0/1)\)\(u\) 为根的子树内,\(0\)(修改边 \((u-fa_u)\))/ \(1\)(修改边 \((u-v)\)),满足子树内直径小于等于 \(lim\) 的约束下,\(u\) 向下的最长链的最小值。转移可以类似树上背包的那种合并(其实绝大部分树上 dp 都可以,主要是有时候碰上可以 dsu on tree 或者长链剖分优化的时候这个写法非常方便)写个 Merge。暴力判断几种情况即可:

  • 对于 \(f(u,0)\) 的转移,其必须从上一个 \(f(u,0)\) 转移过来

  • 对于 \(f(u,1)\) 的转移,可以是 \(u\) 选择了当前的 \((u,v)\),那么从上一个 \(f(u,0)\) 转移过来;也可能 \(u\) 和之前的点选择了,那么从上一个 \(f(u,1)\) 转移过来。

不多讲了直接看代码里的 Merge 函数吧(((

#include<bits/stdc++.h>
#define rep(i,a,b) for(ll i=(a);i<=(b);i++)
#define per(i,a,b) for(ll i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)
#define next Cry_For_theMoon
#define il inline
#define pb(x) push_back(x)
#define is(x) insert(x)
#define sit set<int>::iterator
#define mapit map<int,int>::iterator
#define pi pair<int,int>
#define ppi pair<int,pi>
#define pp pair<pi,pi>
#define fr first
#define se second
#define vit vector<int>::iterator
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef double db;
using namespace std;
const ll MAXN=1e5+10,MAXM=2e5+10,INF=1e18;
ll T,n,p[MAXN],u,v,w;
struct Edge{
	int u,v,w;
}edge[MAXM];
int first[MAXN],next[MAXM],tot;
ll f[MAXN][2],tmp[2];
void addedge(int u,int v,int w){
	edge[++tot]=(Edge){u,v,w};
	next[tot]=first[u];first[u]=tot;
}
void clear(){
	rep(i,1,n)first[i]=0;
	rep(i,1,tot)next[i]=0;
	tot=0;
}
il void Read(ll& num){
	char ch;
	while((ch=getchar()) && (ch<'0' || ch>'9'));
	num=ch-'0';
	while((ch=getchar()) && (ch>='0' && ch<='9'))num=num*10+ch-'0';
}
void Merge(int u,int v,int w,ll mid){
	tmp[0]=f[u][0];tmp[1]=f[u][1];
	f[u][0]=f[u][1]=INF; 
	//0向上
	if(tmp[0]+Min(f[v][0]+Max(0,w-p[v]),f[v][1]+w)<=mid){
		f[u][0]=Min(f[u][0],Max(tmp[0],Min(f[v][0]+Max(0,w-p[v]),f[v][1]+w)));
	}
	//1向下 
	//case 1:当前
	if(tmp[0]+Min(f[v][0]+Max(0,w-p[v]-p[u]),f[v][1]+Max(0,w-p[u]))<=mid){
		f[u][1]=Min(f[u][1],Max(tmp[0],Min(f[v][0]+Max(0,w-p[v]-p[u]),f[v][1]+Max(0,w-p[u]))));
	}
	//case 2:之前
	if(tmp[1]+Min(f[v][0]+Max(0,w-p[v]),f[v][1]+w)<=mid){
		f[u][1]=Min(f[u][1],Max(tmp[1],Min(f[v][0]+Max(0,w-p[v]),f[v][1]+w)));
	}
}
void dfs(int u,int fa,ll mid){
	f[u][0]=f[u][1]=0;
	for(int j=first[u];j;j=next[j]){
		int v=edge[j].v;if(v==fa)continue;
		dfs(v,u,mid);
		Merge(u,v,edge[j].w,mid);
	}
}
int check(ll mid){
	dfs(1,0,mid);
	return f[1][0]<=mid || f[1][1]<=mid;
}
int main(){
	Read(T);
	while(T--){
		Read(n);
		clear();
		rep(i,1,n){
			Read(p[i]);
		}
		rep(i,1,n-1){
			Read(u);Read(v);Read(w);
			addedge(u,v,w);
			addedge(v,u,w);
		}
		ll L=0,R=n*1e5,ret=-1;
		while(L<=R){
			ll mid=(L+R)>>1;
			if(check(mid)){
				ret=mid;
				R=mid-1;
			}else{
				L=mid+1;
			}
		}
		printf("%lld\n",ret);
	}

	return 0;
}

推荐阅读