bzoj1999: [Noip2007]Core树网的核/2282: [Sdoi2011]消防

时间:2021-02-06 18:42:56

noip版:洛谷1099
加强版:bzoj1999
双倍经验(与bzoj1999相同):bzoj2282
对于n<=300的,跑一遍floyd,枚举所有在直径上的线段即可

#include<bits/stdc++.h>
using namespace std;
int n,s,x,y,z,dis[303][303],mx,a[303],b[303],cnt,i,j,k,l,p,ans;
int main(){
    scanf("%d%d",&n,&s);
    memset(dis,63,sizeof(dis));
    for (i=1;i<n;i++) scanf("%d%d%d",&x,&y,&z),dis[x][y]=dis[y][x]=z,dis[i][i]=0;
    dis[n][n]=0;
    for (k=1;k<=n;k++)
        for (i=1;i<=n;i++)
            for (j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
            if (dis[i][j]<1e9) mx=max(mx,dis[i][j]);
    for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
            if (dis[i][j]==mx) a[++cnt]=i,b[cnt]=j;
    ans=1e9;
    for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
            if (dis[i][j]<=s)
                for (l=1;l<=cnt;l++)
                    if (dis[a[l]][i]+dis[i][j]+dis[j][b[l]]==mx){//在直径上
                        p=0;
                        for (k=1;k<=n;k++)
                            if (k!=i && k!=j) p=max(p,(dis[k][i]+dis[k][j]-dis[i][j])>>1);
                        ans=min(ans,p);
                    }
    printf("%d",ans);
}

对于n<=500000。
在[SDOI2011]消防中,没说路径在直径上,可以先证一遍(出处):

显然可以发现这条路径必然在树的直径上
至于证明,只需要假设路径Q不在直径上,那么假设距离最远的点不在直径上,易证不可能,因此距离这条路径Q最远的点一定在直径上。
之后再假设一个A点是不在直径上的一个点,我们假设直径上有一条路径P,如果A到P的距离大于了直径的端点到Q的距离,显然是矛盾的,因此任意的A点到P的距离一定小于直径端点到Q的距离,因此得证。

我反正是看不懂。。。
下面是具体实现(出处):
先bfs两遍求出树的直径
然后维护 g[i]表示i是路径右端点时,右边那段删掉的直径长度。
f[i]表示i是路径左端点时,左边那段删掉的直径长度。
h[i]表示i是直径上的点,每个直径上的点不是都有一棵(或者很多棵)
由非此直径上点组成的树(森林)嘛,点i到这些子节点中最远的那个的距离。
然后在这个序列上跑双指针。就是路径长度不是有限制嘛,然后从左到右枚举左端点,然后右端点是非严格单调右移的。
时间复杂度线性。而对于一段路径区间[l,r],它作为枢纽时的答案为
max(max(f[l],g[r]),max{hi,i∈[l,r]})
然后最右边那个怎么搞呢? 单调队列,维护hi最大值

#include<bits/stdc++.h>
using namespace std;
const int N=500003;
struct node{
    int to,ne,w;
}e[N*2];
int tot,num,i,s,l,r,L,R,len,f[N],g[N],h[N],tmp[N],head[N],fa[N],vis[N],q[N],n,m,x,y,z,t,be[N],ans;
inline int read(){
    int x=0,f=1;char c;
    do{if(c=='-')f=-1;c=getchar();}while(c>'9'||c<'0');
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
void add(int x,int y,int z){
    e[++tot]=(node){y,head[x],z};
    head[x]=tot;
}
int bfs(int s,int num){
    int h=0,t=1,len=0;
    q[1]=s;g[s]=0;fa[s]=s;vis[s]=num;
//要注意g[s]=0,每次g都重新算一遍的,不同地方的g数组可能代表的意义不同
    while (h<t){
        int u=q[++h];
        for (int i=head[u],v;i;i=e[i].ne)
            if (vis[v=e[i].to]!=num){
                vis[v]=num;
                g[v]=g[u]+e[i].w;
                len=max(len,g[v]);
                fa[v]=u;
                q[++t]=v;
            }
    }
    return len;
}
void solve(){
    int s,t=0;
    bfs(s=1,++num);
    for (int i=1;i<=n;i++)
        if (g[i]>g[s]) s=i;
    bfs(s,++num);
    for (int i=1;i<=n;i++)
        if (g[i]>g[t]) t=i;
    be[n=1]=t;num++;
    do t=fa[t],be[++n]=t,vis[t]=num;while (t!=s);
    for (int i=1;i<=n;i++) f[i]=g[be[1]]-g[be[i]];
    for (int i=1;i<=n;i++) tmp[i]=g[be[i]];
    for (int i=1;i<=n;i++) h[i]=bfs(be[i],num);
    for (int i=1;i<=n;i++) g[i]=tmp[i];
}
int main(){
    n=read();m=read();
    for (i=1;i<n;i++){
        x=read();y=read();z=read();
        add(x,y,z);add(y,x,z);
    }
    solve();
    ans=1e9;
    for (l=1;l<=n;l++){
        while (r<n && f[r+1]-f[l]<=m){
            r++;
            while (L<=R && h[q[R]]<h[r]) R--;
            q[++R]=r;
        }
        ans=min(ans,max(max(f[l],g[r]),h[q[L]]));
        if (q[L]<=l) L++;
    }
    printf("%d",ans);
}