[BZOJ4326][NOIP2015]运输计划(二分+dfs序+树上差分)

来源:互联网 发布:雅各布矩阵和海森矩阵 编辑:程序博客网 时间:2024/05/16 07:23

题目描述

传送门

题解

首先,要求最大值最小,先想到二分。可以预处理出来所有路径所需要的时间。
关键是怎么check呢?

算法一
每次二分出mid了之后,将所有时间>=mid的路径上的边打标记(链剖+bit即可),然后枚举每一条边,如果这条边打的标记的数量=所有需要打标记的路径的数量,并且这条边是所有满足条件里的最大的,那么将这条边砍断。然后再判断这个最解是否可行就行了。
然而这个时间复杂度是O(logTmlogn)的,会被卡掉。

算法二
我们考虑在算法一的基础上进行优化。算法一的瓶颈主要是check的时候是O(mlogn)的,查询的时候套了一个bit的log。那么能不能进行O(m)的check呢?
当然可以!方法就是——差分!这里用了一个非常常用的树上差分的思路:对于点x,y设r=lca(x,y)。在x+1,y+1,r-2,然后统计后缀和(x->f[x])。这样就可以把每一条路径都打上标记,然后查询就可以了。

虽然Po的代码在BZ上没快多少,但是在UOJ上可以发现算法二是明显更优的。
(比如愚蠢的ATP写了愚蠢的算法一结果被卡T辣!——ATP▽)

代码

#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define N 300005#define sz 19int n,m,x,y,z,Max,dfs_clock,ans;int tot,point[N],nxt[N*2],v[N*2],c[N*2];int h[N],dis[N],val[N],num[N],tmp[N],f[N][sz+5];struct hp{int x,y,lca,dis;}edge[N];void addedge(int x,int y,int z){    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;}void build(int x,int fa){    num[++dfs_clock]=x;    for (int i=1;i<sz;++i)    {        if ((h[x]-(1<<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;            h[v[i]]=h[x]+1;dis[v[i]]=dis[x]+c[i];val[v[i]]=c[i];            build(v[i],x);        }}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 ((1<<i)&k) 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 cnt=0,limit=0;memset(tmp,0,sizeof(tmp));    for (int i=1;i<=m;++i)        if (edge[i].dis>mid)        {            ++tmp[edge[i].x];++tmp[edge[i].y];tmp[edge[i].lca]-=2;            limit=max(limit,edge[i].dis-mid);            cnt++;        }    if (!cnt) return true;    for (int i=n;i>1;--i) tmp[f[num[i]][0]]+=tmp[num[i]];    for (int i=2;i<=n;++i)        if (val[i]>=limit&&tmp[i]==cnt) return true;    return false;}int find(){    int l=0,r=Max,mid,ans;    while (l<=r)    {        mid=(l+r)>>1;        if (check(mid)) ans=mid,r=mid-1;        else l=mid+1;    }    return ans;}int main(){    scanf("%d%d",&n,&m);    for (int i=1;i<n;++i)    {        scanf("%d%d%d",&x,&y,&z);        addedge(x,y,z);Max+=z;    }    build(1,0);    for (int i=1;i<=m;++i)    {        scanf("%d%d",&edge[i].x,&edge[i].y);        edge[i].lca=lca(edge[i].x,edge[i].y);        edge[i].dis=dis[edge[i].x]+dis[edge[i].y]-dis[edge[i].lca]*2;    }    ans=find();    printf("%d\n",ans);}

总结

树上差分的常见思路:
①利用dfs序的时间戳,一个点拆成两个点,每次在in+1,在out-1,然后bit统计前缀和,资瓷动态修改和查询子树访问次数。用于子树打标记。
②对于点x,y设r=lca(x,y)。在x+1,y+1,r-2,然后统计后缀和(x->f[x])。用于树链打标记,资瓷查询某条边的访问次数。
③对于点x,y设r=lca(x,y)。在x+1,y+1,r-1,father[r]-1,然后统计后缀和(x->f[x])。用于树链打标记,资瓷查询某个点的访问次数。

0 0
原创粉丝点击