2016-2017 ACM-ICPC Northeastern European Regional Contest Problem C. Cactus Construction
题目大意
初始有
N
N
N 个点互不联通,且颜色均为
1
1
1 ,各自处于各自的只有一个点的集合中,给出三种操作方式: 1、把集合
x
x
x 和集合
y
y
y 合并,但不连边; 2、把
x
x
x 所在的集合中,所有颜色
c
1
c1
c 1 染成颜色
c
2
c2
c 2 ; 3、把
x
x
x 所在的集合中,任意一个颜色
c
1
c1
c 1 的点和任意一个颜色为
c
2
c2
c 2 的点之间连边,但不能连出重边。 求把
N
N
N 个初始的点连接形成给出一棵仙人掌的方案,要求操作次数不超过
1
0
6
10^6
1 0 6 ,颜色最多为
4
4
4 种。
N
≤
50000
N\le50000
N ≤ 5 0 0 0 0
题解
可以先想一棵树怎么操作,按照DFS的顺序,每操作结束一棵子树时,需满足根的颜色为
2
2
2 ,其余颜色为
1
1
1 。 然后考虑如何把每棵子树依次往当前节点上连:先把当前点颜色改为
3
3
3 ,接着所有儿子和它自己都加入同一个集合中,再让颜色
2
2
2 和
3
3
3 连边,最后把颜色
2
2
2 改为
1
1
1 ,颜色
3
3
3 改为
2
2
2 ,继续下去即可实现整棵树的构造。 至于仙人掌上的环,先从环上除了环的根以外的每个点遍历下去,回溯时同样需满足子树根为
2
2
2 其余为
1
1
1 的条件,最后借助颜色
3
3
3 和
2
2
2 把除了环的根以外的点依次连上,环的根两端的点颜色连完后改为
4
4
4 ,其他连完后都改为
1
1
1 ,最后把
3
3
3 和
4
4
4 相连即可。 把整体的树的做法和局部的环的做法二者结合即可做到仙人掌的构造。 环的根,如图中以
1
1
1 为整棵树的根遍历,则
2
,
3
,
9
,
10
2,3,9,10
2 , 3 , 9 , 1 0 为环的根。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Npqlu7Gr-1615896917223)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20210316200947487.png)] 注意一个点向下伸展出多个环,即同一个点可以作为多个环的根,记得不要把这些环连在一起。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 50010
int last[ N] , nxt[ N * 4 ] , to[ N * 4 ] , len = 1 ;
int last1[ N] , nxt1[ N * 2 ] , to1[ N * 2 ] , len1 = 0 ;
int vi[ N] , st[ N] , bz[ N] , pd[ N] , o[ N] , tot = 0 , si[ N] ;
struct {
int o, k, x, y;
} s[ N * 20 ] ;
void add ( int x, int y)
{
to[ ++ len] = y;
nxt[ len] = last[ x] ;
last[ x] = len;
}
void add1 ( int x, int y) {
to1[ ++ len1] = y;
nxt1[ len1] = last1[ x] ;
last1[ x] = len1;
}
void dfs ( int k, int fa) {
vi[ k] = bz[ k] = 1 ;
st[ ++ st[ 0 ] ] = k;
for ( int i = last[ k] ; i; i = nxt[ i] ) if ( to[ i] != fa) {
int x = to[ i] ;
if ( bz[ x] ) {
si[ x] ++ ;
pd[ x] = 1 ;
int j = st[ 0 ] ;
while ( st[ j] != x) {
o[ st[ j] ] = si[ x] ;
add1 ( x, st[ j] ) ;
j-- ;
}
}
if ( ! vi[ x] ) dfs ( x, k) ;
}
st[ 0 ] -- ;
bz[ k] = 0 ;
}
void solve ( int k, int fa) {
s[ ++ tot] = { 0 , k, 1 , 3 } ;
for ( int i = last[ k] ; i; i = nxt[ i] ) if ( to[ i] != fa && o[ to[ i] ] == 0 ) {
solve ( to[ i] , k) ;
s[ ++ tot] = { 1 , 0 , k, to[ i] } ;
}
s[ ++ tot] = { 2 , k, 2 , 3 } ;
s[ ++ tot] = { 0 , k, 2 , 1 } ;
int la, fi = 0 ;
for ( int i = last1[ k] ; i; i = nxt1[ i] ) {
solve ( to1[ i] , k) ;
if ( fi == 0 ) {
fi = to1[ i] ;
s[ ++ tot] = { 0 , to1[ i] , 2 , 3 } ;
}
else {
s[ ++ tot] = { 1 , 0 , to1[ i] , la} ;
s[ ++ tot] = { 2 , la, 2 , 3 } ;
if ( la == fi) s[ ++ tot] = { 0 , la, 3 , 4 } ; else s[ ++ tot] = { 0 , la, 3 , 1 } ;
if ( nxt1[ i] == 0 || o[ to1[ nxt1[ i] ] ] != o[ to1[ i] ] ) {
s[ ++ tot] = { 0 , la, 2 , 4 } ;
s[ ++ tot] = { 1 , 0 , to1[ i] , k} ;
s[ ++ tot] = { 2 , k, 4 , 3 } ;
s[ ++ tot] = { 0 , k, 4 , 1 } ;
fi = 0 ;
}
else s[ ++ tot] = { 0 , la, 2 , 3 } ;
}
la = to1[ i] ;
}
s[ ++ tot] = { 0 , k, 3 , 2 } ;
}
int main ( ) {
int n, m, i, j, t, x;
scanf ( "%d%d" , & n, & m) ;
for ( i = 1 ; i <= m; i++ ) {
scanf ( "%d" , & t) ;
int la;
for ( j = 1 ; j <= t; j++ ) {
scanf ( "%d" , & x) ;
if ( j > 1 ) add ( x, la) , add ( la, x) ;
la = x;
}
}
dfs ( 1 , 0 ) ;
solve ( 1 , 0 ) ;
printf ( "%d\n" , tot) ;
for ( i = 1 ; i <= tot; i++ ) {
if ( s[ i] . o == 0 ) printf ( "r %d %d %d\n" , s[ i] . k, s[ i] . x, s[ i] . y) ;
else if ( s[ i] . o == 1 ) printf ( "j %d %d\n" , s[ i] . x, s[ i] . y) ;
else printf ( "c %d %d %d\n" , s[ i] . k, s[ i] . x, s[ i] . y) ;
}
return 0 ;
}
自我小结
这是一道关于仙人掌的题,它的思路比较自然。 先想树的的特殊情况,然后再考虑把做法放到环内,从而找到环的做法。