题解 P1099 树网的核 && P2491 消防

大家都好强啊QAQ,这次题解大多看了lyd的书


P1099 && P2491 题解

Luogu树网的核 && Luogu消防

前排安利博客,LuitaryiJack && Chrisk && 喵の耳

或许会有更好的阅读体验


简化题意

emmmmmm,似乎没有办法简化题意(雾)

一个无环联通无向图,定义直径上一段长度不超过\({S}\)的路为\({F}\)(也可以是一个点),定义图中到\({F}\)的距离最长的为偏心距。求一个\({F}\),使得图上到\({F}\)的距离最大值最小,并输出这个距离

分析 是抄LYD+自己理解的

初步结论:

  1. 树的各个直径中点都交于一点

    • 证明(@LuitaryiJack):如果两条直径没有交于一点,那么就代表你可以构造一条直径,长为原直径的长加上两个中点之间的距离,此时原直径就不符合直径的定义。所以树的各个直径中点都交于一点
  2. 任意一条直径上求出的最小偏心距都相等

    • 证明:由结论\({1}\)可知,所有的直径都交于一点。所以其他的点到这些直径的距离就可以化为到中点的距离。又因为中点都相交于一点。所以我们可以知道任意一条直径上求出的偏心距都是相等的。

      初步做法

  3. O(\({N^{3}}\))

    在树网的核这道题上,由于图的点数过少,所以我们可以用O(\({N^{3}}\))的方法水过这道题。即暴力枚举:先求出树的直径,再在上面枚举F,然后在\({F}\)上每一个点用\({dfs}\)求图上到\({F}\)的最远的点的距离,再取最小值即可

  4. O(\({N^{2}}\))

    然后考虑对枚举暴力进行优化。由于贪心可知,如果每次\({F}\)的选取越长,通过感性理解我们求的的偏心距就越小。所以我们每次定\({F}\)长为\({S}\),然后枚举\({dfs}\)求偏心距

二级结论

  • 这题满足单调的性质:偏心距越大,可以得到的点的位置越集中,使得由这些点构成的\({F}\)越短,即\({S}\)越小

    • 证明:QAQ感觉性质就是证明啊在二分\({mid}\)作为偏心距的大前提下,我们首先定义一些东西:

      1. 直径两个端点为\({q}\)\({v}\)
      2. 在直径上,离\({q}\)最长的不超过\({mid}\)的点为\({p}\),同理,\({u}\)是相对\({v}\)而言的那个点
    • 根据直径的最长性,任何从\({p}\)\({q}\)之间的直径中分叉离开的子树,其离\({p}\)最远的距离不会超过\({mid}\)
    • 所以\({p}\)\({u}\)就是在偏心距不超过\({mid}\)的前提下,尽量靠近\({F}\)的点

二级做法 什么鬼标题

  • O(\({NlogSUM}\)),sum为树网中所有边的长度之和

    二分\({mid}\),然后按照上面的结论证明找\({p}\)\({u}\),然后判断他们之间的距离是否超过\({S}\),以及离\({F}\)最远的的点到这俩点的距离是否合法。若两者都满足,那么代表这是合法的

最终的做法 打这个题解好累啊QAQ

  • O(\({N}\))

    设直径上的节点为\({u_{1},u_{2}.....u_{t}}\),那么先和前几种做法一样,把这些节点标记为已经访问,然后用深度优先遍历求\({d[u_{i}]}\),含义为从\({u_{i}}\)出发,不经过直径上的点,可以到达的最远距离。显然,偏心距要么就是由结论\({2}\)得到的直径两端点到\({F}\)的距离的较大值,要么就是直径外到\({F}\)的最大的距离。所以,首先枚举\({F}\)的两个端点取\({max}\)。最后由于最后要用\({F}\)完全遍历一遍直径,所以我们取\({max(d[u_{i}])}\)。然后扫一边直径即可求解


code

我选取了我们机房大佬们的\({code}\)给大家做参考。要是有侵权,emmmmm,那就侵权吧手动滑稽

1 O(\({N^{3}}\)) @喵の耳

#include 
using namespace std;
namespace gengyf{
    inline int read(){
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    const int inf=0x3f3f3f3f;
    const int N=310;
    int m[N][N],n,s,ans=inf;
    int main(){
        n=read();s=read();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
                m[i][j]=inf;
                if(i==j)m[i][j]=0;
            }
        for(int i=1;i=inf||m[i][j]<=len)continue;
                len=m[i][j];
                l=i;r=j;
        }
        for(int i=1;i<=n;i++){
            if(m[l][i]+m[i][r]!=len)continue;
            for(int j=1;j<=n;j++){
                if(m[l][j]+m[r][j]!=len)continue;
                if(m[i][j]>s)continue;
                int ecc=-inf;
                for(int k=1;k<=n;k++){
                    ecc=max(ecc,m[i][k]+m[j][k]-m[i][j]>>1);
                }
                ans=min(ans,ecc);
            }
        }
        printf("%d",ans);
        return 0;
    }
}
int main(){
    gengyf::main();
    return 0;
}

markdown的代码插入不知道怎么折叠QAQ,好丑

2 O(\({N^{2}}\)) @LuitaryiJack


#include
#include
#include
#define R register int 
using namespace std;
const int N=310;
inline int g() {
    R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix;
    do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
}
struct edge {
    int v,w,nxt;
    #define v(i) e[i].v
    #define w(i) e[i].w
    #define nxt(i) e[i].nxt
}e[N<<1];
int n,m,k,st,ed,mx,ans=0x3f3f3f3f,tot,cnt,fir[N],pre[N],cnte[N],d[N],mem[N],sume[N];
inline void add(int u,int v,int w) {v(++tot)=v,w(tot)=w,nxt(tot)=fir[u],fir[u]=tot;}
bool vis[N];
void dfs(int u,int fa) {
    //if(vis[u]) return ;
    for(R i=fir[u];i;i=nxt(i)) {
        R v=v(i);
        if(vis[v]||v==fa) continue;
        d[v]=d[u]+w(i);
        dfs(v,u);
        pre[v]=u;
        cnte[v]=i;
    }
}
inline void solve() {
    memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d)); mx=0;
    d[1]=0,dfs(1,0);
    for(R i=1;i<=n;++i) if(d[i]>mx) mx=d[i],st=i;
    memset(pre,0,sizeof(pre));
    memset(d,0,sizeof(d)); mx=0;
    d[st]=0,dfs(st,0);
    for(R i=1;i<=n;++i) if(d[i]>mx) mx=d[i],ed=i;
    for(R i=ed;i;i=pre[i]) mem[++cnt]=i,sume[cnt]=sume[cnt-1]+w(cnte[i]);
    for(R i=1;i<=cnt;++i) { R j; mx=0;
        memset(vis,false,sizeof(vis));
        memset(d,0,sizeof(d));
        for(j=i;j<=cnt;++j) if(sume[j-1]-sume[i-1]>k) break; 
        --j;
        for(R t=i;t<=j;++t) vis[mem[t]]=true;
        for(R t=i;t<=j;++t) dfs(mem[t],0);
        for(R i=1;i<=n;++i) if(d[i]>mx) mx=d[i];
        ans=min(mx,ans);
    }
}
signed main() {
    n=g(),k=g();
    for(R i=1,u,v,w;i

3 O(\({NlogSUM}\)) @LuitaryiJack

#include
#include
#include
#define R register int
using namespace std;
int n,s,l=1,r=1;
int fa[300010],vis[300010],fir[300010],cnt;
long long d[300010],ans=0x3f3f3f3f;
inline int g() {
    R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix;
    do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
}
struct node {
    int v,w,nxt;
    #define v(i) e[i].v
    #define w(i) e[i].w
    #define nxt(i) e[i].nxt
}e[600010];
inline void add(int u,int v,int w) {v(++cnt)=v,w(cnt)=w,nxt(cnt)=fir[u],fir[u]=cnt;}
inline void dfs(int u) {
    for(R i=fir[u];i;i=nxt(i)) {
        R v=v(i);
        if(vis[v]||fa[u]==v) continue;
        fa[v]=u;
        d[v]=d[u]+w(i);
        dfs(v);
    }
}
signed main() {
    n=g(),s=g();
    for(R i=1,u,v,w;id[l]?i:l;
    memset(fa,0,sizeof(fa));
    d[l]=0,dfs(l);
    for(R i=1;i<=n;++i) r=d[i]>d[r]?i:r;
    R t=r;
    for(R i=r;i;i=fa[i]) {
        while(fa[t]&&d[i]-d[fa[t]]<=s) t=fa[t];
        ans=min((long long)ans,max(d[t],d[r]-d[i]));
    }
    for(R i=r;i;i=fa[i]) vis[i]=true;
    for(R i=r;i;i=fa[i]) d[i]=0,dfs(i);
    for(R i=1;i<=n;++i) if(!vis[i]) ans=max(ans,d[i]);
    printf("%lld\n",ans);
}

4 O(\({N}\)) @我自己又丑又长又慢的代码QAQ

#include
#include
#include
using namespace std;
const int maxn=500010;
int head[maxn],ver[maxn<<1],nxt[maxn<<1],tot;
int edge[maxn<<1],fa[maxn],dp[maxn],val[maxn];
int dep[maxn],n,m,x,y,z,tmp,ans,s;bool v[maxn];
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f*x;
}
inline void add(int x,int y,int z){
    ver[++tot]=y,nxt[tot]=head[x];
    head[x]=tot;edge[tot]=z;
}
inline void dfs(int x){
    for(register int i=head[x];i;i=nxt[i]){
        if(ver[i]!=fa[x]){
            dep[ver[i]]=dep[x]+edge[i];
            fa[ver[i]]=x,dfs(ver[i]);
        }
    }
}
inline void diameter(){
    dfs(1);x=1;
    for(register int i=2;i<=n;i++)
        if(dep[i]>dep[x]) x=i;//离根最远的点
    dep[x]=0;memset(fa,0,sizeof(fa));
    //换了根,所以fa会变
    dfs(x);y=1;//两遍dfs求树的直径
    for(register int i=2;i<=n;i++)
        if(dep[i]>dep[y]) y=i;//离x最远的点
    while(y!=x) v[y]=1,val[++m]=y,y=fa[y];
    //把直径上x,y之间的点全部标记上访问过
    //单开val[]记一下直径上都有什么
    v[x]=1;val[++m]=x;
}
inline void treedp(int x){v[x]=1;
    for(register int i=head[x];i;i=nxt[i])
        if(!v[ver[i]]) treedp(ver[i]),
            dp[x]=max(dp[x],dp[ver[i]]+edge[i]);
    //在直径上dp,
    //dp[]->从[]出发,不过直径上的点,可以到达的最远的点的距离
}
int main(){
    n=read(),s=read();ans=0x3f3f3f3f;
    for(register int i=1;i=1;i--){
        while(j>=1&&dep[val[j]]-dep[val[i]]<=s) j--;
        ans=min(ans,max(tmp,
            max(dep[val[i]],dep[val[1]]-dep[val[j+1]])));
    }
    printf("%d",ans);
}

好像和chrisk没有关系啊,QAQ,算了,前排安利博客就安利了吧,反正也没有收钱都是同学QAQ


最后国际惯例,thanks for your attention

总算写完了,累死了

你可能感兴趣的:(题解 P1099 树网的核 && P2491 消防)