题目:
这次就给既不支持time和next变量名的洛谷吧
题解:
一次A满意,该题即为删除一条边使得所有航线中最长的一条最短
最大值最小,显然可以二分:二分答案,然后check的时候把所有大于mid值的路径记录下来,找出被所有这样路径覆盖的最长的道路:如果没有这样的道路 false;如果这样的道路被减去之后依然大于mid false
找出被所有路径覆盖的道路:在树中将所有路径起、始权值+1,LCA权值-2,从所有叶节点往上累加(dfs序维护真是好),最终权值为路径数的点到其父亲的边为所求边
dis[i]表示i到根的距离;tmp[i]表示i这个点通往父亲的边,目的是记录这条边被遍历的次数 ;num[i]的作用是找到叶节点向上累加
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#define N 300005
#define sz 24
using namespace std;
struct hh
{
int x,y,lca,dis;
}lu[N];
int cc,tot,nxt[N*2],point[N*2],v[N*2],tim[N*2],num[N],h[N],mi[sz];
int f[N][sz],dis[N],tmp[N],m,n;
void addline(int x,int y,int cap)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; tim[tot]=cap;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; tim[tot]=cap;
}
void build(int x,int fa,int dep)//f[x][i]表示x倍增i倍的爸爸
{
num[++cc]=x;
h[x]=dep;
for (int i=1;i<sz;i++)
{
if (h[x]-mi[i]<1) break;
f[x][i]=f[f[x][i-1]][i-1];
}
for (int i=point[x];i;i=nxt[i])
if (v[i]!=fa)
{
f[v[i]][0]=x;
dis[v[i]]=tim[i]+dis[x];
build(v[i],x,dep+1);
}
}
int lca(int x,int y)
{
if (h[x]<h[y]) swap(x,y);
int k=h[x]-h[y];
for (int i=0;i<sz;i++)
if ((k>>i)&1)
x=f[x][i];
if (x==y) return x;
for (int i=sz-1;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
bool check(int mid)
{
int i,cnt=0,limit=0;
memset(tmp,0,sizeof(tmp));
for (int i=1;i<=m;i++)
if (lu[i].dis>mid)
{
++tmp[lu[i].x]; ++tmp[lu[i].y]; tmp[lu[i].lca]-=2;
limit=max(limit,lu[i].dis-mid);
cnt++;
}
if (!cnt) return true;
for (i=n;i>=1;i--) tmp[f[num[i]][0]]+=tmp[num[i]];
for (i=2;i<=n;i++) if (tmp[i]==cnt && dis[i]-dis[f[i][0]]>=limit) return true;
return false;
}
int main()
{
int i,Z=0;
mi[0]=1; for (i=1;i<=sz;i++) mi[i]=mi[i-1]*2;
scanf("%d%d",&n,&m);
for (i=1;i<=n-1;i++)
{
int x,y,cap;
scanf("%d%d%d",&x,&y,&cap);
addline(x,y,cap); Z+=cap;
}
dis[1]=0;
build(1,0,1);
for (i=1;i<=m;i++)
{
scanf("%d%d",&lu[i].x,&lu[i].y); lu[i].lca=lca(lu[i].x,lu[i].y);
lu[i].dis=dis[lu[i].x]+dis[lu[i].y]-2*dis[lu[i].lca];
}
int l=0,r=Z,mid;
while (l<r)
{
mid=(l+r)>>1;
if (check(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
}
这里引用树上差分思路:
①利用dfs序的时间戳,一个点拆成两个点,每次在in+1,在out-1,然后bit统计前缀和,资瓷动态修改和查询子树访问次数。用于子树打标记。
②对于点x,y设r=lca(x,y)。在x+1,y+1,r-2,然后从所有叶节点往上累加。用于树链打标记,资瓷查询某条边的访问次数。
③对于点x,y设r=lca(x,y)。在x+1,y+1,r-1,father[r]-1,然后从所有叶节点往上累加。用于树链打标记,资瓷查询某个点的访问次数。