【题解】CJOI2019 鸡贼的树(构造题)

【题解】CJOI2019 鸡贼的树(构造题)

鸡贼有一棵\(n\)个节点的树。
鸡贼认为这棵树太不安全了,因为每一条边都是割边,球贼任意偷走树上的一条边都可以使这棵树不连
通。为了保护这棵树,鸡贼决定添加一些边,使这棵树变成一张无重边,自环的图,且删掉任意一条边
后它仍然联通。
但是,每添加一条边,都要一定的费用。鸡贼还想省下钱来买 Play Station 4 ,所以他想知道,至少要添
加多少条边呢?仅仅知道数量当然是不够的,在一些测试点中,他还想知道应该怎样添加边。

显然最优答案的下界是\(\lfloor \dfrac {c+1} 2\rfloor\)\(c\)是叶子节点个数(保证个根度数不为1)。(考虑方法:断开一个叶子节点的父边)

考虑一个这样的构造方法,将一个叶子节点连接到和它的\(LCA\)是根的另一个节点,加入剩下了一个点就连向根。找到一个根节点,使得它每个子树内的叶子节点小于\(\lfloor \dfrac {c+1} 2\rfloor\)

直接求\(dfs\)序,把得到的叶子加入数组\(ve\),将\(ve[i]\)\(ve[i+\lfloor \dfrac {c+1} 2\rfloor]\)连接,若多出来,连接根节点即可。

显然\(ve[i]\)\(ve[i+\lfloor \dfrac {c+1} 2\rfloor]\)\(LCA\)是根。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;  typedef long long ll;
inline int qr(){
      register int ret=0,f=0;
      register char c=getchar();
      while(c<48||c>57)f|=c==45,c=getchar();
      while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar();
      return f?-ret:ret;
}

const int maxn=5e5+5;
int dr[maxn];
struct E{
      int to,nx;
      E(){to=nx=0;}
      E(const int&a,const int&b){to=a;nx=b;}
}e[maxn<<1];
vector<int> ve;
int head[maxn];
int n,rt,ans,op,cnt;
inline void add(const int&fr,const int&to,const int&b){
      e[++cnt]=E(to,head[fr]);
      head[fr]=cnt;
      ++dr[fr];
      if(dr[fr]>1)rt=fr;
      if(b)add(to,fr,0);
}

void dfs(const int&now,const int&last){
      if(dr[now]==1) ve.push_back(now);
      for(register int t=head[now];t;t=e[t].nx)
        if(e[t].to!=last) dfs(e[t].to,now);
}

int main(){
      //freopen("tree.in","r",stdin);
      //freopen("tree.out","w",stdout);
      n=qr();op=qr();
      for(register int t=1;t<n;++t) add(qr(),qr(),1);
      dfs(rt,0);
      int sz=ve.size();
      printf("%d\n",(sz+1)>>1);
      if(!op)return 0;
      for(register int t=0;t<(sz>>1);++t)
        printf("%d %d\n",ve[t],ve[t+(sz>>1)]);
      if(sz&1) printf("%d %d",ve[sz-1],rt);
      return 0;
}

猜你喜欢

转载自www.cnblogs.com/winlere/p/11299132.html