P3525 INS-Inspection

这道题的题面有点问题,如果按照题面做,应该是A不了的,下面引用一下评论里@REM_001的翻译

一棵n个节点的树,行动中心S从1->N。从S出发前往任意一个未标记到的点(沿树上两点的唯一路径走),标记该节点,然后返回S。相邻两次行动所经过的道路不允许有重复,最后一次标记后不需要返回,求路程总和的最小值。

第i行输出行动中心为i时的答案,如果不可能则输出-1

这种翻译好像跟题面没什么区别啊。

别着急,下面就知道区别在哪里了。

题面要求,相邻两次到达的点不能有公共边,说白了就是以 \(S\) 为根的树每次取的点不能来自同一颗 \(S\) 的子节点的子树。那么再简化一下,就是 \(S\) 的子节点的子树节点数最大不能超过\(n/2\)。为什么呢?如果超过一半了,肯定会出现相邻啊。

那么这里就出现了一个临界值。\(n-1\)个点排成的队列,如果\(n\)是偶数,比如说是\(4\)。那么最大的子节点的子树大小最大就是\(2\)这个时候,队列的最后一个点就被确定一定是这个大小为\(2\)的子树上的点。意识到什么不对的地方了吗?

如果按照题面翻译的那样,我们是不需要考虑这种情况的,但是正确的题面可是要考虑这个临界值的啊。这也就是有的人怎么改都A不了的原因了。题面都是错的,怎么可能写对嘛。

上面我们讲了怎么判定,那么怎么求最大的子节点的子树大小呢?设 \(s[u]\) 表示以\(u\)为根的子树大小,我们先随便选一个点为根节点,然后dfs一遍求出\(s[u]\),此时的\(s[u]\)表示的是以\(u\)的父节点为根节点时,以\(u\)为根的子树大小。当以\(u\)的子节点为根节点时,以\(u\)为根的子树大小为\(n-s[u]+1\),这里很好理解,就不说为什么了。

能够判定了,我们还需要求距离和,还有最大距离。

我们定义\(w[u][0]\)表示以\(u\)为根节点的子树上的点到\(u\)的距离和,\(w[u][1]\)表示不在以\(u\)为根节点的子树上的点到\(u\)的距离和,跑两遍dfs就可以求出。然后就是求距离,这个很简单,我们在下面的程序里说

下放程序

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 1000005
using namespace std;

inline ll read(){
    ll a=0;int f=0;char p=gc();
    while(!isdigit(p)){f|=p=='-';p=gc();}
    while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
    return f?-a:a;
}int n;ll ans;

struct ahaha{
    int to,next;
}e[maxn<<1];int tot,head[maxn];
inline void add(int u,int v){
    e[tot].to=v,e[tot].next=head[u];head[u]=tot++;
}

int f[maxn],l[maxn],s[maxn];ll w[maxn][2];  //f记录父子关系,l记录最远距离,s记录子树大小,w[0]表示下方的点的距离和,w[1]表示上方的点的距离和
int p[maxn],d1[maxn],d[maxn][2];  //p表示下方距离的最大值经过哪一个子节点,d1表示下方距离次大值,d[0]表示下方距离最大值,d[1]表示上方距离最大值
void dfs(int u,int fa){s[u]=1;
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;if(v==fa)continue;
        dfs(v,u);f[v]=u;s[u]+=s[v];  //先递归,再处理 s的处理也很常见
        w[u][0]+=w[v][0]+s[v];  //加上s[v]是加上uv相连的这条边的贡献
        if(d[v][0]+1<=d1[u])continue; //如果v下方最大值+1还不如u下方次大值大,我们就可以直接走了
        if(d[v][0]+1>=d[u][0]){  //如果大于等于最大值,更新次大值,更新最大值,更新最大值来源。为什么等于也可以大家可以自己想一想,很简单
            d1[u]=d[u][0];
            d[u][0]=d[v][0]+1,p[u]=v;
            continue;
        }
        d1[u]=d[v][0]+1;  //剩下的情况就是 d1[u]<d[v][0]+1<d[u][0] 所以更新次大值
    }
}
void dfs1(int u,int fa){
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;if(v==fa)continue;
        if(v==p[u])d[v][1]=max(d1[u],d[u][1])+1;  //如果是父节点的最大值来源,那么用次大值更新
        else d[v][1]=max(d[u][0],d[u][1])+1;  //反之,用最大值更新
        w[v][1]=(ll)n-s[v]+w[u][1]+w[u][0]-w[v][0]-s[v];  //n-s[v]表示uv相连的边的贡献,w[u][0]-w[v][0]-s[v]表示父节点的其他子节点的贡献
        dfs1(v,u);  //先处理,再递归
    }
}

int main(){memset(head,-1,sizeof head);
    n=read();
    if(n==1){  //对于只有一个节点的树我们直接特判
        puts("0");
        return 0;
    }   
    for(int i=1;i<n;++i){  //读入所有边
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    dfs(1,-1);dfs1(1,-1);  //第一遍dfs自下而上处理,第二遍自上而下
    for(int u=1;u<=n;++u){
        int maxa=0,a,k;
        for(int i=head[u];~i;i=e[i].next){  //求出以当前节点为根,最大的子节点的子树大小,并记录是哪个子节点
            int v=e[i].to;k=f[v]==u?s[v]:n-s[u];
            if(k>maxa)maxa=k,a=v;
        }
        if((maxa<<1)>n){  //如果超过一半,必定会相邻,输出-1
            puts("-1");
            continue;
        }
        l[u]=(maxa<<1)==n?(f[a]==u?d[a][0]+1:(max(p[a]==u?d1[a]:d[a][0],d[a][1])+1)):max(d[u][0],d[u][1]);  //这一行的意思是如果不是前面提到的临界值,那么直接找最大距离即可;但是如果是临界情况,并且这个子节点依然是子节点,那么就是子节点下面的最远距离+1,如果子节点之前是父节点,那么找子节点没有经过u的最长距离+1
        printf("%lld\n",(w[u][0]+w[u][1]<<1)-l[u]); //最后输出答案即可
    }
    return 0;
}

这道题留给我们一个教训,就是不要过分相信翻译,毕竟谁都可以递交翻译

看完不妨留个推荐?

posted @ 2019-08-28 11:25 子谦。 阅读(...) 评论(...) 编辑 收藏
Live2D
//雪