题目描述:
给你一棵n个点的带权无根树和m条树上两点之间的路径,你可以将树上任意两个直接相连的点之间的路径变成权值为0,并尽可能使得改变后路径长度的最大值最小,输出这个最大值。
数据范围如下:
20%解法(测试点1-4):
十分暴力的做法:枚举改哪条边,然后对于每一条路径都暴力在原图上搜索一遍,求出长度,所有长度求个max,然后对所有修改方案求个min即可。时间复杂度:
50%-60%解法(测试点1-12,但测试点9,10有些写的丑的可能卡T):
依然是枚举修改的边,但是考虑在30分做法求路径长的方法上进行改进,每枚举一条边就重新建一棵树,固定1节点为根,并重造倍增lca的数组,同时每个点记录从这点到根的距离dis【i】,考虑一个从u节点到v节点的路径,它的长度就是dis【u】+dis【v】-2dis【lca(u,v)】。时间复杂度:
95%-100%解法:
我们可以发现如果输出的答案为ans,那么修改的边必然被输入路径中原长为ans+1到max的路径覆盖。这样我们发现只需要二分答案,然后把比这个答案长的路径在树上做一次差分,然后dfs一遍原图,去除被这些所有路径覆盖的最长边,假设最长边长度为len,原路径最长为max,二分判断的路径长为mid,如果max-len>mid则说明ans要比mid大否则就小于等于mid。具体操作样例说话:
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
建出如下树:
然后将路径长度排序,第一条长度为11,第二条长度为10,第3条长度为11,然后把比二分的mid大的路径全部差分:
差分具体操作:一开始所有点权为0,当处理到u与v之间的路径时,在u节点点权+1,v节点点权+1,lca(u,v)节点点权-2。那么处理完路径长大于mid的路径后再dfs一遍,每个节点的子树权值之和就是这个节点连向父亲的边被覆盖的路径数。例如求每条边被这3条路径覆盖了多少次:
首先算第一条路径,从3到6,那么节点3,6点权+1,节点1点权-2。(橙色的数表示节点权值)
于是节点的权值变成了这样:
第二条路径,节点2,3点权+1,节点1点权-2。
变成这样:
第三条路径,节点4,5点权+1,节点3点权-2。
变成这样:
dfs求一遍每个节点的子树点权之和,就是这个节点连向父亲的边的被覆盖的路径数啦!
(紫色为每个节点的子树点权和)
这样差分就完成了,剩下的就是简单的操作:把被所有长度大于mid的路径覆盖的边求一个最大边权len。如果max-len>mid;则可以判断ans>mid否则ans<=mid(其中max为原图最长路径,mid为二分判断的答案,ans为最后输出的答案)。
这题用倍增求lca是的复杂度,会T掉最后一个点,所以应该用欧拉序求lca,查询一次lca是O(1)的复杂度,预处理欧拉序lca是的,每次二分+差分+dfs时间复杂度是时间复杂度就可以降到的复杂度,可以过100%的数据,这就是95分和100分的小区别(当年NOIP很多这题都是95分的)。
推荐番:《某科学的超电磁炮》
上代码:
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
struct data
{int u,v,len;};
data a[300010];
int dfn,pan[600010],to[600010],pp[600010],next[600010],line[600010],fir[600010],zhi[600010],val[600010];
int rmq[600010][23],lcc[600010][23],dep[600010],dui[600010],n,m,x,y,v,p,u,gen[600010];
int qian[600010],bian,chafen[600010],ma,l,r,mid;
bool cmp(data a,data b)
{
return a.len<b.len;
}
void dfs(int k)
{
pan[k]=1;
dfn++;line[dfn]=k;fir[k]=dfn;
int pu=pp[k];
while (pu>0)
{
if (pan[to[pu]]==0)
{
gen[to[pu]]=gen[k]+val[pu];
zhi[to[pu]]=val[pu];
dep[to[pu]]=dep[k]+1;
dfs(to[pu]);
dfn++;line[dfn]=k;
}
pu=next[pu];
}
}
void dfs2(int k,int zz)
{
pan[k]=1;
int pu=pp[k];
qian[k]=chafen[k];
while (pu>0)
{
if (pan[to[pu]]==0)
{
dfs2(to[pu],zz);
qian[k]+=qian[to[pu]];
}
pu=next[pu];
}
if (qian[k]==zz){bian=max(bian,zhi[k]);}
}
void rr()
{
for (int i=1;i<=dfn;i++){rmq[i][0]=dep[line[i]];lcc[i][0]=line[i];}
for (int i=1;(1 << i)<=dfn;i++)
{
for (int j=1;j<=dfn;j++)
{
if (rmq[j][i-1]<rmq[j+(1 << (i-1))][i-1])
{
rmq[j][i]=rmq[j][i-1];
lcc[j][i]=lcc[j][i-1];
}
else
{
rmq[j][i]=rmq[j+(1 << (i-1))][i-1];
lcc[j][i]=lcc[j+(1 << (i-1))][i-1];
}
}
}
int po=2;
for (int i=2;i<=dfn;i++)
{
if (i==po){po*=2;dui[i]=dui[i-1]+1;}
else dui[i]=dui[i-1];
}
}
int lca (int u,int v)
{
if (fir[u]>fir[v])swap(u,v);
int er=dui[fir[v]-fir[u]+1];
if (rmq[fir[u]][er]<rmq[fir[v]-(1 << er)+1][er])return lcc[fir[u]][er];
else return lcc[fir[v]-(1 << er)+1][er];
}
bool check(int ll)
{
int lef=0,rig=m+1,mm;
while (lef+1<rig)
{
mm=(lef+rig)/2;
if (a[mm].len>ll)rig=mm;
else lef=mm;
}
if (a[lef].len<=ll)lef++;
for (int i=1;i<=n;i++)chafen[i]=0;
for (int i=lef;i<=m;i++)
{
chafen[a[i].u]++;chafen[a[i].v]++;
chafen[lca(a[i].v,a[i].u)]-=2;
}
bian=0;
for (int i=1;i<=n;i++){pan[i]=0;qian[i]=0;}
dfs2(1,m-lef+1);
if (ma-bian<=ll)return true;
return false;
}
int main()
{
cin>>n>>m;
for (int i=1;i<n;i++)
{
scanf("%d %d %d",&x,&y,&v);
p++;to[p]=y;val[p]=v;next[p]=pp[x];pp[x]=p;
p++;to[p]=x;val[p]=v;next[p]=pp[y];pp[y]=p;
}
dep[1]=1;
dfs(1);
rr();
for (int i=1;i<=m;i++)
{
scanf("%d %d",&a[i].u,&a[i].v);
a[i].len=gen[a[i].u]+gen[a[i].v]-2*(gen[lca(a[i].u,a[i].v)]);
ma=max(ma,a[i].len);
}
sort(a+1,a+1+m,cmp);
l=-1;r=ma+1;
while (l+1<r)
{
mid=(l+r)/2;
if (check(mid))r=mid;
else l=mid;
}
printf("%d\n",r);
return 0;
}