2016-2017 ACM-ICPC Northeastern European Regional Contest Problem C. Cactus Construction(仙人掌+构造)

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 c1染成颜色 c 2 c2 c2
  • 3、把 x x x所在的集合中,任意一个颜色 c 1 c1 c1的点和任意一个颜色为 c 2 c2 c2的点之间连边,但不能连出重边。
  • 求把 N N N个初始的点连接形成给出一棵仙人掌的方案,要求操作次数不超过 1 0 6 10^6 106,颜色最多为 4 4 4种。
  • N ≤ 50000 N\le50000 N50000

题解

  • 可以先想一棵树怎么操作,按照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,10为环的根。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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;
}
// finish time {root = 2; else = 1}
// r 0
// j 1
// c 2
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;
}

自我小结

  • 这是一道关于仙人掌的题,它的思路比较自然。
  • 先想树的的特殊情况,然后再考虑把做法放到环内,从而找到环的做法。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值