Codeforces Round #720 (Div. 2) D. Nastia Plays with a Tree 题解

一、算法分析

基本算法是树上统计+贪心。笔者参赛时是不会做的,后来参考了洛谷too_late 的博客 的 https://www.luogu.com.cn/blog/115857/solution-cf1521d题解才码出这道题。下面简单地描述一下思路:

1.划分链的种类,可以将链划分为1号链和2号链。

2.对于每个子树,最好的情况是能保留成1个1号链,或1个二号链。所以对于一棵子树,设子树的根结点为u,u的子结点为集合{v},则对于所有的v,其可能是1号链的根结点(记为1号v)或2号链的根结点(记为2号v)。然后分情况讨论:

(1)只有一个1号v,则带上u作为一个1号链保留(作为主链)。

(2)只有两个1号v,则带上u作为一个2号链保留(作为主链)。

(3)有大于等于3个1号v,则将其中两个作为2号链保留(作为主链),剩下的接在2号链的某一边的下面。

(4)在1~3的情况下存在2号v,则将2号v整体代表的2号链拆下来接在主链上。

(5)只有2号v,选择其中一个2号v作为主子链,将其它的2号v代表的2号链拆下来接到这个主子链的左半边,最后将这个主子链的右半边接到左半边,最后就只剩下一个一号子链,其连接着u结点形成了一个一号链。

总的来说,这道题既考察了思维能力,又考察了代码能力,是一道好题。这种树上统计类型的题目的代码方式也值得学习积累。

二、代码及注释

  1 #include<iostream>
  2 #include<cstring>
  3 #include<algorithm>
  4 #include<cstdio>
  5 using namespace std;
  6 const int N=200050;
  7 struct node{
  8     int a,b,c,d;
  9     
 10 }res[N];
 11 int cnt;
 12 const int M=N<<1;
 13 int h[N],e[M],ne[M],idx;
 14 void add(int a,int b){
 15     e[idx]=b,ne[idx]=h[a],h[a]=idx++;
 16 }
 17 int T;
 18 int n;
 19 int type[N];                                   //以i为中心的链是几号链
 20 int son[N][2];                                 //2号链的左右儿子
 21 int s[N],t[N];                                 //以i为中心的链的起点和终点
 22 
 23 void dfs(int u,int fa){
 24     type[u]=1;
 25     s[u]=u;
 26     t[u]=u;
 27     int sons=0;
 28     int sum=0;
 29     for(int i=h[u];~i;i=ne[i]){
 30         int j=e[i];
 31         if(j==fa) continue;
 32         dfs(j,u);
 33         sons++;                                //儿子个数
 34         if(type[j]==1) sum++;
 35     }
 36     if(sum>=2){                                //如果多于两条链,就要合并
 37         int x=0,y=0;
 38         type[u]=2;                             //合并成就剩一个二号链
 39         for(int i=h[u];~i;i=ne[i]){            //选其中两条一号链作为最后保留的二号链
 40             int j=e[i];
 41             if(j==fa) continue;
 42             if(type[j]==1){
 43                 if(!x) x=j;
 44                 else{
 45                     y=x;
 46                     x=j;
 47                     break;
 48                 }
 49             }
 50         }
 51         son[u][0]=x,son[u][1]=y;
 52         s[u]=t[y],t[u]=t[x];
 53         for(int i=h[u];~i;i=ne[i]){
 54             int j=e[i];
 55             if(j==fa || j==x || j==y) continue;
 56             res[++cnt]={j,u,t[j],t[u]};        //要求第一个和第三个不能相等
 57             t[u]=s[j];
 58         }
 59     }
 60     else if(sum==1){                           //只有一个一号链的情况,还要去掉二号链
 61         int x=0;
 62         for(int i=h[u];~i;i=ne[i]){
 63             int j=e[i];
 64             if(j==fa) continue;
 65             if(type[j]==1){
 66                 x=j;
 67                 break;
 68             }
 69         }
 70         t[u]=t[x];
 71         for(int i=h[u];~i;i=ne[i]){
 72             int j=e[i];
 73             if(j==fa ||j==x) continue;
 74             //接下来就只剩下二号链了
 75             res[++cnt]={j,u,s[j],t[u]};
 76             t[u]=t[j];
 77         }
 78     }
 79     else if(sons){                              //只有二号链的情况,选定一个二号链,然后将其它二号链转移进去
 80         int x=0;
 81         for(int i=h[u];~i;i=ne[i]){
 82             int j=e[i];
 83             if(j==fa) continue;
 84             x=j;
 85             break;
 86         }
 87         for(int i=h[u];~i;i=ne[i]){
 88             int j=e[i];
 89             if(j==fa || j==x) continue;
 90             res[++cnt]={j,u,s[j],s[x]};
 91             s[x]=t[j];                        
 92         }
 93         res[++cnt]={son[x][1],x,s[x],t[x]};     //二号链变为一号链
 94         t[u]=son[x][1];
 95     }
 96     
 97 }
 98 int main(){
 99     
100     scanf("%d",&T);
101     while(T--){
102         memset(h,-1,sizeof(h));
103         cnt=0;
104         scanf("%d",&n);
105         for(int i=1;i<n;i++){
106             int a,b;
107             scanf("%d%d",&a,&b);
108             add(a,b);
109             add(b,a);
110         }
111         dfs(1,-1);
112         printf("%d\n",cnt);
113         for(int i=1;i<=cnt;i++) printf("%d %d %d %d\n",res[i].a,res[i].b,res[i].c,res[i].d);
114     }
115     
116     
117     
118     
119     return 0;
120     
121 }

(之前写在小号里了,现在大号找回来了,所以再搬回来汇总一下)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值