一、算法分析
基本算法是树上统计+贪心。笔者参赛时是不会做的,后来参考了洛谷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 }
(之前写在小号里了,现在大号找回来了,所以再搬回来汇总一下)