题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=4123
题意
题意大概就是给你一棵树,有 n n n 个顶点、 n − 1 n-1 n−1 条边,每条边有一个权值,现在让你求出每个点距离其他点的最长距离,然后有 m m m 组询问,每次询问给一个数 q q q ,每次询问在顶点 [1,n] 这个范围内,其中连续的一段(顶点编号连续) 最长距离 的最大值和最小值之差,在不大于 q q q 的情况下,最大化这个区间。
解析
(1)第一步
要求出每个点距离其他点的最长距离,有原题:Hdu 2196
具体做法就是,先固定根(一般选
1
1
1 号点),第一遍 dfs 维护每个点的最长距离和次长距离,第二遍 dfs 求每个点经过其父亲路径的最长距离。
原因:因为固定根了,所以最长距离一定是由儿子得来的,而我们要求每个点距离其他点的最长距离无非就是 “由儿子得来的最长距离” 和 “由经过父亲得来的最长距离” 两者最大的一者。
而求“由经过父亲得来的最长距离”,我们需要求每个点由儿子得来的次长距离。例如:
假如我们要求
2
2
2 号点的最长距离,除了它儿子过来的最大值(蓝线),还有就是其父亲的路径(红线部分),蓝线部分很好求,而红线部分其中就包含以
1
1
1 号点为根时,距离
1
1
1 号点的次长距离(就是
d
i
s
(
1
,
3
)
dis(1,3)
dis(1,3) ),还有
d
i
s
(
1
,
2
)
dis(1,2)
dis(1,2)。
具体代码为:
//dp[u][0]:定根时,距离u点的最长距离
//dp[u][1]:定根时,距离u点的次长距离
//dp[u][2]:定根时,经过u点父亲距离u点的最长距离
void dfs(int u,int fa){//求dp[u][0]和dp[u][1]
dp[u][0]=dp[u][1]=0;
for(int i=0;i<(int)g[u].size();i++){
int v=g[u][i].ft,w=g[u][i].sd;
if(v==fa)continue;
dfs(v,u);
if(dp[u][0]<dp[v][0]+w){//更新最长和次长
dp[u][1]=dp[u][0],dp[u][0]=dp[v][0]+w;
}
else if(dp[u][1]<dp[v][0]+w){//更新次长
dp[u][1]=dp[v][0]+w;
}
}
}
void _dfs(int u,int fa){//求dp[u][2]
for(int i=0;i<(int)g[u].size();i++){
int v=g[u][i].ft,w=g[u][i].sd;
if(v==fa)continue;
//当前点在父节点的最长路上,要用到父节点的次长路
if(dp[u][0]==dp[v][0]+w){
dp[v][2]=max(dp[u][1],dp[u][2])+w;
}
//当前点不在父节点的最长路上,要用到父节点的最长路
else {
dp[v][2]=max(dp[u][2],dp[u][0])+w;
}
_dfs(v,u);
}
}
(2)第二步
求区间最小值,和最大值,可以用线段树、ST表。线段树代码量贼多,直接用ST表了。求解的目的是因为我们要使区间最大最小差值在满足 不大于 询问值 的情况下,最大化,所以尺取的前提是知道区间最大最小的差值。所以就依
1
−
n
1-n
1−n 点的最长距离建ST表。
int Log2(int x){// 求 log2(x)
int bit=1,ans=0;
while(bit<=x){
bit<<=1;
ans++;
}
return ans-1;
}
struct RMQ{//求区间最大最小值
ll ma[maxn][21],mi[maxn][21];
void init(){
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
ma[i][j]=0;
mi[i][j]=inf;
}
}
//最后每个点的最长距离是max(dp[u][0],dp[u][2]),我提前把它更新在了dp[u][0]
for(int i=1;i<=n;i++)ma[i][0]=mi[i][0]=dp[i][0];
for(int j=1;j<=20;j++){
for(int i=(1<<j);i<=n;i++){
ma[i][j]=max(ma[i][j-1],ma[i-(1<<(j-1))][j-1]);
mi[i][j]=min(mi[i][j-1],mi[i-(1<<(j-1))][j-1]);
}
}
}
ll query_ca(int l,int r){//求差值
int x=Log2(r-l+1);//此处用库函数会超时,贼坑
ll mma=max(ma[r][x],ma[l+(1<<x)-1][x]);
ll mmi=min(mi[r][x],mi[l+(1<<x)-1][x]);
return mma-mmi;
}
}rmq;
(3)第三步
尺取法擅长求满足一定条件的最大区间,需要用两个指针
l
,
r
l,r
l,r,不断移动
l
,
r
l,r
l,r 中间更新最大区间,直至
l
,
r
l,r
l,r 超出范围。
void solve(){//每组询问
int q;scanf("%d",&q);
int l=1,r=1,ans=0;
while(l<=n&&r<=n){
while(r<=n&&rmq.query_ca(l,r)<=q){
r++;
}
if(r-l>ans){//更新最优解
ans=r-l;
}
l++;
}
printf("%d\n",ans);
}
完整代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#define ft first
#define sd second
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=5e4+7;
const int inf=0x3f3f3f3f;
int n,m;
ll dp[maxn][3];
vector<P> g[maxn];
int Log2(int x){// 求 log2(x)
int bit=1,ans=0;
while(bit<=x){
bit<<=1;
ans++;
}
return ans-1;
}
struct RMQ{//求区间最大最小值
ll ma[maxn][21],mi[maxn][21];
void init(){
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
ma[i][j]=0;
mi[i][j]=inf;
}
}
//最后每个点的最长距离是max(dp[u][0],dp[u][2]),我提前把它更新在了dp[u][0]
for(int i=1;i<=n;i++)ma[i][0]=mi[i][0]=dp[i][0];
for(int j=1;j<=20;j++){
for(int i=(1<<j);i<=n;i++){
ma[i][j]=max(ma[i][j-1],ma[i-(1<<(j-1))][j-1]);
mi[i][j]=min(mi[i][j-1],mi[i-(1<<(j-1))][j-1]);
}
}
}
ll query_ca(int l,int r){//求差值
int x=Log2(r-l+1);//此处用库函数会超时,贼坑
ll mma=max(ma[r][x],ma[l+(1<<x)-1][x]);
ll mmi=min(mi[r][x],mi[l+(1<<x)-1][x]);
return mma-mmi;
}
}rmq;
//dp[u][0]:定根时,距离u点的最长距离
//dp[u][1]:定根时,距离u点的次长距离
//dp[u][2]:定根时,经过u点父亲距离u点的最长距离
void dfs(int u,int fa){//求dp[u][0]和dp[u][1]
dp[u][0]=dp[u][1]=0;
for(int i=0;i<(int)g[u].size();i++){
int v=g[u][i].ft,w=g[u][i].sd;
if(v==fa)continue;
dfs(v,u);
if(dp[u][0]<dp[v][0]+w){//更新最长和次长
dp[u][1]=dp[u][0],dp[u][0]=dp[v][0]+w;
}
else if(dp[u][1]<dp[v][0]+w){//更新次长
dp[u][1]=dp[v][0]+w;
}
}
}
void _dfs(int u,int fa){//求dp[u][2]
for(int i=0;i<(int)g[u].size();i++){
int v=g[u][i].ft,w=g[u][i].sd;
if(v==fa)continue;
//当前点在父节点的最长路上,要用到父节点的次长路
if(dp[u][0]==dp[v][0]+w){
dp[v][2]=max(dp[u][1],dp[u][2])+w;
}
//当前点不在父节点的最长路上,要用到父节点的最长路
else {
dp[v][2]=max(dp[u][2],dp[u][0])+w;
}
_dfs(v,u);
}
}
void solve(){//每组询问
int q;scanf("%d",&q);
int l=1,r=1,ans=0;
while(l<=n&&r<=n){
while(r<=n&&rmq.query_ca(l,r)<=q){
r++;
}
if(r-l>ans){//更新最优解
ans=r-l;
}
l++;
}
printf("%d\n",ans);
}
int main() {
while(~scanf("%d%d",&n,&m)){
if(!n&&!m)break;
for(int i=1;i<=n;i++)g[i].clear();
for(int i=1;i<=n-1;i++){
int u,w,v;scanf("%d%d%d",&u,&v,&w);
g[u].pb(P(v,w));
g[v].pb(P(u,w));
}
dfs(1,0);
_dfs(1,0);
//最长距离统一存在dp[i][0]
for(int i=1;i<=n;i++){
dp[i][0]=max(dp[i][0],dp[i][2]);
}
rmq.init();
while(m--){
solve();
}
}
return 0;
}