[bzoj2282][Sdoi2011]消防(树上乱搞+二分)

题目:

我是超链接

题解:

这个题目的关键:我们选择的路径一定在直径上。可以用反证法
如果整条路径与直径没有交集,那么可以从其中一点走到直径上,然后走到直径一端,发现那个点到直径一端的距离一定大于从直径上一点直接到ta的距离,因为直径上另一边的一端没有选择路径那一部分作为直径的另一半
如果有交集,此路径与直径会在一个点岔开,对于岔开的点一定需要通过ta到达直径的某一端,而如果选择直径的话就是到达该路径的一端,我们的该路径怎么会比直径长呢?
所以路径一定在树的直径上!

我们可以dfs求出树的直径,然后(熟练地)记录下这条链上的点,求出这些点到离ta最远的叶子结点(不经过直径上的点)的值
整个直径的长度很有可能超过s,所以我们要考虑截取直径的一段。
看到这种最大值最小的问题,首先应该会想到二分,我们二分最大值,然后考虑从直径的两端向内缩,直到不能缩为止,判断此时的长度与s的关系看是否可行。
那么什么情况就不能缩了呢,就是连在这个点的最远的叶子节点的长度(包括已经从直径中t出去的点)>二分的答案的时候就不能再缩了。
注意len记录的是到达gen的距离,所以在上面的距离小,下面的距离大,应该用距离大的-距离小的

代码:

#include 
#include 
#include 
#define N 300005
using namespace std;
int tot,nxt[N*2],point[N],v[N*2],c[N*2],len[N],maxx,gen,father[N],cha[N],maxn[N],dis[N],sy[N],s;
bool vis[N];
void addline(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 dfs(int x,int fa)
{
    if (len[x]>maxx) maxx=len[x],gen=x;
    father[x]=fa;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa)
      {
        len[v[i]]=len[x]+c[i];
        dfs(v[i],x);
      }
}
void dfs2(int x,int fa)
{
    vis[x]=1;
    maxx=max(maxx,dis[x]);
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && !vis[v[i]])
      {
        dis[v[i]]=dis[x]+c[i];
        dfs2(v[i],x);
      }
}
void Chain(int x)
{
    while (x)
    {
        vis[x]=1;
        cha[++cha[0]]=x;
        x=father[x];
    }
}
bool check(int mid)
{
    int l=1,r=cha[0],i;
    for (i=1;i<=cha[0];i++)
    {
        sy[cha[i]]=maxn[cha[i]];
        if (sy[cha[i]]>mid) return 0;
    }
    while (1)
    {
        sy[cha[l+1]]=max(sy[cha[l+1]],sy[cha[l]]+len[cha[l+1]]-len[cha[l]]);
        if (sy[cha[l+1]]>mid || l+1>r) break;
        l++;
    }
    for (i=1;i<=cha[0];i++) sy[cha[i]]=maxn[cha[i]];
    while (1)
    {
        sy[cha[r-1]]=max(sy[cha[r-1]],sy[cha[r]]+len[cha[r]]-len[cha[r-1]]);
        if (sy[cha[r-1]]>mid || l+1>r) break;
        r--;
    }
    return (len[r]-len[l])<=s;//链长短了,可以缩的太多了,你这个mid值太大了啊 
}
int main()
{
    int n,i,l=0,r=0;
    scanf("%d%d",&n,&s);
    for (i=1;iint x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        addline(x,y,z);r+=z;
    }
    dfs(1,0);memset(len,0,sizeof(len));maxx=0;dfs(gen,0);
    Chain(gen);
    for (i=1;i<=cha[0];i++)
    {
        maxx=0;
        dfs2(cha[i],0);
        maxn[cha[i]]=maxx;
    }  
    memset(len,0,sizeof(len));maxx=0;dfs(gen,0);
    int ans=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }

    printf("%d",ans);
}

你可能感兴趣的:(图论,二分/三分)