Solution: 题解 CF1029E Tree with Small Distances

核心思想:贪心

先将树转化为以\(1\)为根的有根树

考虑如果想要连上一个点\(u\),并且\(u\)的所有子树都已经通过其他方式连上了

那么就有三种方法:

  1. 连上\(u\)自己,那么可以顺势连上\(u\)的父亲\(f_u\)

  2. 连上\(u\)的一个儿子,那么除了连上\(u\)以外没有任何效果

  3. 连上\(u\)的父亲\(f_u\),那么\(f_u\)的所有儿子都可以顺带连上

综上,连上\(f_u\)是贪心最优的

给每一个点\(u\)一个深度\(d_u\),其中\(d_1=0\)

为了保证\(u\)的所有子树都连上,可以建以\(d_u\)为键值的大根堆,把所有\(d_u>2\)\(u\)丢进去

每次弹出一个\(u\),就把\(f_u\)连上,即将\(f_u\)及其周围的点打上标记

如果弹出的\(u\)打过了标记,直接忽略即可

Time complexity: \(O(n\log n)\)

Memory complexity: \(O(n)\)

细节见代码(\(2.82\)s / \(17.77\)MB)

//This program is written by Brian Peng.
#pragma GCC optimize("Ofast","inline","no-stack-protector")
#include
using namespace std;
#define Rd(a) (a=read())
#define Gc(a) (a=getchar())
#define Pc(a) putchar(a)
int read(){
    register int x;register char c(getchar());register bool k;
    while(!isdigit(c)&&c^'-')if(Gc(c)==EOF)exit(0);
    if(c^'-')k=1,x=c&15;else k=x=0;
    while(isdigit(Gc(c)))x=(x<<1)+(x<<3)+(c&15);
    return k?x:-x;
}
void wr(register int a){
    if(a<0)Pc('-'),a=-a;
    if(a<=9)Pc(a|'0');
    else wr(a/10),Pc((a%10)|'0');
}
signed const INF(0x3f3f3f3f),NINF(0xc3c3c3c3);
long long const LINF(0x3f3f3f3f3f3f3f3fLL),LNINF(0xc3c3c3c3c3c3c3c3LL);
#define Ps Pc(' ')
#define Pe Pc('\n')
#define Frn0(i,a,b) for(register int i(a);i<(b);++i)
#define Frn1(i,a,b) for(register int i(a);i<=(b);++i)
#define Frn_(i,a,b) for(register int i(a);i>=(b);--i)
#define Mst(a,b) memset(a,b,sizeof(a))
#define File(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
#define N (200010)
int n,u,v,f[N],d[N],ans;
bool vs[N];
vectore[N];
struct Cmp{bool operator()(int a,int b){return d[a],Cmp>q;
void dfs(int u);
signed main(){
    Rd(n);
    Frn0(i,1,n)e[Rd(u)].push_back(Rd(v)),e[v].push_back(u);
    dfs(1);
    while(!q.empty()){
        u=q.top(),q.pop();
        if(vs[u])continue;
        vs[f[u]]=1,++ans;
        for(int i:e[f[u]])vs[i]=1;
    }
    wr(ans),exit(0);
}
void dfs(int u){
    if(d[u]>2)q.push(u);
    for(int i:e[u])if(i!=f[u])f[i]=u,d[i]=d[u]+1,dfs(i);
}

接下来是优化部分

发现时间瓶颈在于堆操作是\(O(\log n)\)

而且事实上根本不需要堆,因为只有在dfs时进行的push()操作,之后都是弹出

但是如果对\(u\)按照\(d_u\)排序,用sort()还是\(O(n\log n)\)

其实如果要生成一个按\(d_u\)排序的序列有\(O(n)\)做法,那就是……

BFS大法好!!!!!

BFS序就是一个按照\(d_u\)从小到大排序的序列

所以只要把\(d_u>2\)的BFS序记录下来,然后倒着做就可以了,其它操作不变

Time complexity: \(O(n)\)

Memory complexity: \(O(n)\)

细节见代码(\(2.04\)s / \(10.11\)MB)(效果不错)

//This program is written by Brian Peng.
#pragma GCC optimize("Ofast","inline","no-stack-protector")
#include
using namespace std;
#define Rd(a) (a=read())
#define Gc(a) (a=getchar())
#define Pc(a) putchar(a)
int read(){
    register int x;register char c(getchar());register bool k;
    while(!isdigit(c)&&c^'-')if(Gc(c)==EOF)exit(0);
    if(c^'-')k=1,x=c&15;else k=x=0;
    while(isdigit(Gc(c)))x=(x<<1)+(x<<3)+(c&15);
    return k?x:-x;
}
void wr(register int a){
    if(a<0)Pc('-'),a=-a;
    if(a<=9)Pc(a|'0');
    else wr(a/10),Pc((a%10)|'0');
}
signed const INF(0x3f3f3f3f),NINF(0xc3c3c3c3);
long long const LINF(0x3f3f3f3f3f3f3f3fLL),LNINF(0xc3c3c3c3c3c3c3c3LL);
#define Ps Pc(' ')
#define Pe Pc('\n')
#define Frn0(i,a,b) for(register int i(a);i<(b);++i)
#define Frn1(i,a,b) for(register int i(a);i<=(b);++i)
#define Frn_(i,a,b) for(register int i(a);i>=(b);--i)
#define Mst(a,b) memset(a,b,sizeof(a))
#define File(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
#define N (200010)
int n,u,v,f[N],ans,s[N],sz;
bool vs[N];
struct Q{int u,d;}p;
vectore[N];
queueq;
signed main(){
    Rd(n),q.push({1,0});
    Frn0(i,1,n)e[Rd(u)].push_back(Rd(v)),e[v].push_back(u);
    while(!q.empty()){
        p=q.front(),q.pop();
        if(p.d>2)s[++sz]=p.u;
        for(int i:e[p.u])if(i!=f[p.u])f[i]=p.u,q.push({i,p.d+1});
    }
    Frn_(i,sz,1){
        if(vs[s[i]])continue;
        vs[f[s[i]]]=1,++ans;
        for(int j:e[f[s[i]]])vs[j]=1;
    }
    wr(ans),exit(0);
}

你可能感兴趣的:(Solution: 题解 CF1029E Tree with Small Distances)