原题走这里
本题求的是改造后,使得所有路线的最大值最小。
一看到最大值最小,立刻想到二分答案,我们可以二分所有路线中最长的一条,设为x,判断是否可能通过改造,使得所有路线都小于等于x。问题就被转化为了判定性问题。
接着就是是判断可行性:为了判断可行性,我们要找到这样一条边,使得
1)它被所有大于x的路径包含,
2)原最长路线-本边长度<=x,即改造本边可以可以是所有路径长度都减到x以下,
如果找得到则返回真,否则返回假。
最后,我们该怎么样找到这样一条边呢?我们可以使用树上差分。
在每一次判断的开头,对于每一条长度大于x的路径(u,v)做标记:sign[u]++,sign[v]++,sign[lca[u,v]]-=2。然后按照dfs序倒序遍历,每次将本点的sign累加到父节点上,同时判断本点到父节点的边是否符合条件即可。
具体细节见代码。
代码如下:
#include
using namespace std;
char buf[10000000],*pp=buf;
#define getchar() *(pp++);
inline int read()
{
int X=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')X=X*10+ch-'0',ch=getchar();
return X;
}
struct edge
{
int v,l,p;
}e[600010],q[600010];
int n,m,cnt,head[300010],qhead[300010],qcnt=1,f[300010],p[300010],to[300010],sign[300010],s[300010],U[300010],V[300010],lca[300010],dfn[300010],d[300010],maxx,dfs_clock;
inline void addedge(edge *E,int *H,int &CNT,int u,int v,int l)
{
E[++CNT]=(edge){v,l,H[u]};
H[u]=CNT;
E[++CNT]=(edge){u,l,H[v]};
H[v]=CNT;
}
int find(int u)
{
return f[u]==u?u:f[u]=find(f[u]);
}
void merge(int u,int v)
{
f[find(u)]=find(v);
}
void Tarjan(int u)//Tarjan求LCA
{
f[u]=u;
dfn[++dfs_clock]=u;
for(int i=head[u];i;i=e[i].p)
{
int v=e[i].v;
if(f[v])continue;
s[v]=s[u]+e[i].l;
p[v]=u;
to[v]=e[i].l;
Tarjan(v);
merge(v,u);
}
for(int i=qhead[u];i;i=q[i].p)
{
int v=q[i].v;
if(f[v])
{
lca[i/2]=find(v);
maxx=max(maxx,d[i/2]=s[u]+s[v]-2*s[lca[i/2]]);
}
}
}
bool judge(int x)
{
memset(sign,0,sizeof(sign));
int num=0;
for(int i=1;i<=m;i++)
{
if(d[i]>x)//大于x的路径加标记
{
sign[U[i]]++;
sign[V[i]]++;
sign[lca[i]]-=2;
num++;
}
}
for(int i=n;i;i--)//树上差分,按dfs序倒序遍历以代替递归回溯
{
sign[p[dfn[i]]]+=sign[dfn[i]];
if(to[dfn[i]]>=maxx-x&&sign[dfn[i]]==num)return 1;
}
return 0;
}
int main()
{
fread(buf,sizeof(char),sizeof(buf),stdin);
n=read();
m=read();
for(int i=1;iint u=read(),v=read(),l=read();
addedge(e,head,cnt,u,v,l);
}
for(int i=1;i<=m;i++)
{
addedge(q,qhead,qcnt,U[i]=read(),V[i]=read(),0);
}
Tarjan(1);
int l=0,r=maxx+1;
while(lint mid=(l+r)/2;
if(judge(mid))//二分
{
r=mid;
}
else
{
l=mid+1;
}
}
cout<return 0;
}