一道实际上很清新的题,结果我们三个人赛场上看都没看这道题。
第一眼看到题的时候没思路
题意:
给定大小为 \(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;
}