CERC 2012 题解 (A~K)

比赛地址

CHFFY 3 - Central Europe Regional Contest 2012

CodeForcesGym - 2012-2013 ACM-ICPC, Central Europe Regional Contest (CERC 12)

BZOJ 3115、3169、4057 ~ 4065 也可以提交


A. Kingdoms

题意:

给定n个国家之间的债务关系,如果某个国家负债,则可以破产并解除与其他国家的债务关系,如果有多个国家负债则随机一个破产,问存在哪些局面只剩一个国家。

n <= 20。

题解:

每种局面可以用一个长度为n的二进制串表示出来,两个局面之间的关系可以通过使一个人破产得到,dfs转移时修改债务关系,检查有多少个局面只剩一个国家即可。由于得到每个状态最多修改O(n)个债务关系,所以时间复杂度为O(n*2^n)。

代码:

#include <cstdio>
#include <cstring>
const int maxn = 20;
int t, n, e[maxn][maxn], c[maxn], ans;
bool vis[1 << maxn];
void dfs(int mask, int size)
{
	if(vis[mask])
		return;
	vis[mask] = 1;
	if(size == 1)
	{
		ans |= mask;
		return;
	}
	for(int i = 0; i < n; ++i)
		if((mask & (1 << i)) && c[i] > 0)
		{
			for(int j = 0; j < n; ++j)
				c[j] -= e[j][i];
			dfs(mask ^ (1 << i), size - 1);
			for(int j = 0; j < n; ++j)
				c[j] += e[j][i];
		}
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		ans = 0;
		memset(vis, 0, sizeof vis);
		scanf("%d", &n);
		for(int i = 0; i < n; ++i)
		{
			c[i] = 0;
			for(int j = 0; j < n; ++j)
			{
				scanf("%d", &e[i][j]);
				c[i] += e[i][j];
			}
		}
		dfs((1 << n) - 1, n);
		if(!ans)
			puts("0");
		else
		{
			bool flag = 0;
			for(int i = 0; i < n; ++i)
				if(ans & (1 << i))
				{
					if(flag)
						putchar(' ');
					else
						flag = 1;
					printf("%d", i + 1);
				}
			putchar('\n');
		}
	}
	return 0;
}

B. Who wants to live forever?

题意:

给定一个长度为n的01串S,定义一次操作为,S'_i = S_{i-1} xor S_{i+1},如果S_i不存在则视为0,然后用S'替代S,问S能否操作有限次变成全0的串。

n <= 200000。

题解:

不难发现长度为偶数的串都是循环下去的,如果长度为奇数,则奇偶位置之间互相不影响,把它们分成[n/2]个串分别检查是否全0即可,只要有一个不会全0,整体就不会全0,分治复杂度为O(nlogn)。

代码:

#include <cstdio>
const int maxn = 200010;
int t;
char str[maxn], tmp[maxn];
bool solve(int L, int R)
{
	bool flag = 0;
	for(int i = L; i <= R; ++i)
		if(str[i] == 1)
		{
			flag = 1;
			break;
		}
	if(!flag)
		return 0;
	if(~(R - L + 1) & 1)
		return 1;
	int M, cnt = 0;
	for(int i = L + 1; i <= R; i += 2)
		tmp[cnt++] = str[i];
	M = L + cnt - 1;
	for(int i = L + 1; i <= R; i += 2)
		tmp[cnt++] = str[i - 1] ^ str[i + 1];
	R = L + cnt - 1;
	for(int i = 0; i < cnt; ++i)
		str[L + i] = tmp[i];
	return solve(L, M) || solve(M + 1, R);
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		int i;
		scanf("%s", str);
		for(i = 0; str[i]; ++i)
			str[i] -= '0';
		puts(solve(0, i - 1) ? "LIVES" : "DIES");
	}
	return 0;
}

C. Chemist’s vows

题意:

给定一个长度为n的字符串,问能否用元素周期表里的字符串拼接而成。

n <= 50000。

题解:

打个表,把元素周期表放进去,然后就是匹配了,令f[i]表示前i位能否被表示,每次只用看f[i-1]或者f[i-2]拼一个字符串是否可行,时间复杂度O(n)。

代码:

#include <cstdio>
#include <cstring>
const int maxn = 50010, maxs = 256, maxk = 120;
const char cla[maxk][10] =
{
	 "h", "he", "li", "be",  "b",  "c",  "n",  "o",  "f", "ne",
	"na", "mg", "al", "si",  "p",  "s", "cl", "ar",  "k", "ca",
	"sc", "ti",  "v", "cr", "mn", "fe", "co", "ni", "cu", "zn",
	"ga", "ge", "as", "se", "br", "kr", "rb", "sr",  "y", "zr",
	"nb", "mo", "tc", "ru", "rh", "pd", "ag", "cd", "in", "sn",
	"sb", "te",  "i", "xe", "cs", "ba", "hf", "ta",  "w", "re",
	"os", "ir", "pt", "au", "hg", "tl", "pb", "bi", "po", "at",
	"rn", "fr", "ra", "rf", "db", "sg", "bh", "hs", "mt", "ds",
	"rg", "cn", "fl", "lv", "la", "ce", "pr", "nd", "pm", "sm",
	"eu", "gd", "tb", "dy", "ho", "er", "tm", "yb", "lu", "ac",
	"th", "pa",  "u", "np", "pu", "am", "cm", "bk", "cf", "es",
	"fm", "md", "no", "lr"
};
int t, n;
char str[maxn];
bool f[maxn], a[maxs][maxs];
void init()
{
	for(int i = 0; i < maxk; ++i)
		a[(int)cla[i][0]][(int)cla[i][1]] = 1;
}
int main()
{
	init();
	scanf("%d", &t);
	while(t--)
	{
		memset(f, 0, sizeof f);
		scanf("%s", str + 1);
		n = strlen(str + 1);
		f[0] = 1;
		for(int i = 1; i <= n; ++i)
		{
			if(i > 1)
				f[i] |= a[(int)str[i - 1]][(int)str[i]] & f[i - 2];
			f[i] |= a[(int)str[i]][0] & f[i - 1];
		}
		puts(f[n] ? "YES" : "NO");
	}
	return 0;
}

D. Non-boring sequences

题意:

一个序列被称为是不无聊的,仅当它的每个连续子序列存在一个独一无二的数字,即每个子序列里至少存在一个数字只出现一次。给定一个长度为n的整数序列,请你判断它是不是不无聊的。

n <= 200000。

题解:

无聊的序列比较好判定,即存在一个连续子序列里每个数字都出现了至少两次,那么对于一个序列里只出现一次的数字,恰好把这个序列分开成了两个可能无聊的序列,如果能合理的利用这个数字来切分序列,则可以得到不错的复杂度,于是从左从右同时找这样的数字来切分,可以证明这样的复杂度是O(nlogn)的。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 200001;
int t, n, val[maxn], tot, que[maxn], pos[maxn], l[maxn], r[maxn];
bool boring(int L, int R)
{
	if(L >= R)
		return 0;
	for(int i = L, j = R; i <= j; ++i, --j)
	{
		if(l[i] < L && r[i] > R)
			return boring(L, i - 1) || boring(i + 1, R);
		if(l[j] < L && r[j] > R)
			return boring(L, j - 1) || boring(j + 1, R);
	}
	return 1;
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		tot = 0;
		scanf("%d", &n);
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d", val + i);
			que[tot++] = val[i];
		}
		sort(que, que + tot);
		tot = unique(que, que + tot) - que;
		memset(pos, 0, tot * sizeof(int));
		for(int i = 1; i <= n; ++i)
		{
			val[i] = lower_bound(que, que + tot, val[i]) - que;
			l[i] = pos[val[i]];
			pos[val[i]] = i;
		}
		memset(pos, 0, tot * sizeof(int));
		for(int i = n; i; --i)
		{
			r[i] = pos[val[i]] ? pos[val[i]] : n + 1;
			pos[val[i]] = i;
		}
		puts(boring(1, n) ? "boring" : "non-boring");
	}
	return 0;
}

E. Word equations

题意:

给定n个字符串的生成方式,要么是两个字符串拼接而成,要么是一个常量字符串,保证拼接的关系不成环,问其中一个字符串能否取出一个最长的子序列是给定的另外一个字符串P。

n <= 500, 每个常量字符串长度<= 5, |P| <= 2000。

题解:

可以贪心算出每个字符串从P的第i位开始匹配最远不能到达的下一个位置是谁,如果这个字符串是两个字符串拼接而成,那么就是从左边串匹配完的位置继续在右边匹配得到的位置,记忆化搜索即可,时间复杂度O(nP)。

代码:

#include <cstdio>
#include <cstring>
const int maxn = 2001, maxm = 501, maxs = 6;
int t, n, m, rt, H[maxm], L[maxm], R[maxm], f[maxm][maxn];
char word[maxm][maxs], str[maxn], a[maxs], b[maxs];
bool vis[maxm];
int Hash(char *s)
{
	int res = 0;
	for(int i = 0; s[i]; ++i)
		res = res * 29 + (s[i] - 'A' + 1);
	return res;
}
void dfs(int x)
{
	if(vis[x])
		return;
	vis[x] = 1;
	if(L[x] == -1)
	{
		for(int i = 0; i <= n; ++i)
		{
			int j = i;
			for(int k = 0; str[j] && word[x][k]; ++k)
				if(str[j] == word[x][k])
					++j;
			f[x][i] = j;
		}
	}
	else
	{
		dfs(L[x]);
		dfs(R[x]);
		for(int i = 0; i <= n; ++i)
			f[x][i] = f[R[x]][f[L[x]][i]];
	}
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		memset(L, -1, sizeof L);
		memset(vis, 0, sizeof vis);
		scanf("%d", &m);
		for(int i = 0; i < m; ++i)
		{
			scanf("%s%*s%s", a, b);
			H[i] = Hash(a);
			if(b[0] >= 'a' && b[0] <= 'z')
				strcpy(word[i], b);
			else
			{
				L[i] = Hash(b);
				scanf("%*s%s", b);
				R[i] = Hash(b);
			}
		}
		for(int i = 0; i < m; ++i)
			if(L[i] != -1)
			{
				for(int j = 0; j < m; ++j)
					if(L[i] == H[j])
					{
						L[i] = j;
						break;
					}
				for(int j = 0; j < m; ++j)
					if(R[i] == H[j])
					{
						R[i] = j;
						break;
					}
			}
		scanf("%s%s", a, str);
		rt = Hash(a);
		for(int i = 0; i < m; ++i)
			if(rt == H[i])
			{
				rt = i;
				break;
			}
		n = strlen(str);
		dfs(rt);
		puts(f[rt][0] == n ? "YES" : "NO");
	}
	return 0;
}

F. Farm and factory

题意:

有一个n点m边的带权连通无向图,现在要增加一个点,和一些有权边使得这个点和原来的图连通,要求原图每个点到点1和点2存在一条最短路不经过新点,最小化新点到原图所有点的距离平均值,新边的边权可以是实数,答案误差不超过10^{-8}。

2 <= n <= 10^5, m <= 3*10^5, 边权 <= 10^6。

题解:

设dis(i,j)表示原图中i到j的最短路,dis’(i,j)表示新图中i到j的最短路,新点为c。

可以发现,新加一个点后,这个点不会松弛原图到1或2的最短路的条件是:dis’(u,c)>=|dis(u,1)-dis‘(c,1)|且dis’(u,c)>=|dis(u,2)-dis’(c,2)|。

则要想最小化答案的话就应该是:dis’(u,c)=max{|dis(u,1)-dis‘(c,1)|, |dis(u,2)-dis’(c,2)|},这样答案的合式里有dis’(c,1)和dis’(c,2)是未知数。

可以发现,如果把(dis’(u,1), dis’(u,2))当作二维平面上的点,答案的合式即n个点到某个点的切比雪夫距离之和,而切比雪夫距离可以通过坐标变换(例如坐标轴旋转45度)变成曼哈顿距离,而最小化曼哈顿距离和就是直接按维度找中位数即可。

此题还是喜闻乐见的卡精度题,之前的坐标变换变成两倍,即可全部使用整数,最后输出的时候除以2n即可,总时间复杂度O(nlogn)。

代码:

#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<long long, int> edge;
const int maxn = 100010;
int t, n, m;
long long dis[2][maxn], x[maxn], y[maxn], ans;
vector<edge> e[maxn];
priority_queue<edge, vector<edge>, greater<edge> > Q;
bool vis[2][maxn];
void scan(int &x)
{
	int ch;
	while((ch = getchar()) < '0' || ch > '9');
	x = ch - '0';
	while((ch = getchar()) >= '0' && ch <= '9')
		x = (x << 3) + (x << 1) + ch - '0';
}
int main()
{
	scan(t);
	while(t--)
	{
		ans = 0;
		memset(dis, 0x3f, sizeof dis);
		memset(vis, 0, sizeof vis);
		scan(n), scan(m);
		for(int i = 0; i < n; ++i)
			e[i].clear();
		while(m--)
		{
			int u, v, w;
			scan(u), scan(v), scan(w);
			--u;
			--v;
			e[u].push_back(make_pair(w, v));
			e[v].push_back(make_pair(w, u));
		}
		for(int s = 0; s < 2; ++s)
		{
			while(!Q.empty())
				Q.pop();
			dis[s][s] = 0;
			Q.push(make_pair(dis[s][s], s));
			while(!Q.empty())
			{
				int u = Q.top().second;
				Q.pop();
				if(vis[s][u])
					continue;
				vis[s][u] = 1;
				for(vector<edge>::iterator it = e[u].begin(); it != e[u].end(); ++it)
				{
					int &v = it -> second;
					long long &w = it -> first;
					if(dis[s][v] > dis[s][u] + w)
					{
						dis[s][v] = dis[s][u] + w;
						Q.push(make_pair(dis[s][v], v));
					}
				}
			}
		}
		for(int i = 0; i < n; ++i)
		{
			x[i] = dis[0][i] + dis[1][i]; //2x'
			y[i] = dis[0][i] - dis[1][i]; //2y'
		}
		sort(x, x + n);
		sort(y, y + n);
		long long xx = x[n >> 1], yy = y[n >> 1];
		for(int i = 0; i < n; ++i)
			ans += abs(x[i] - xx) + abs(y[i] - yy);
		printf("%.12f\n", ans / (2.0 * n));
	}
	return 0;
}

G. Jewel heist

题意:

给定平面上的n个点,每个点有一种颜色,最多有k种颜色,找到一条水平线,使得在这条水平线之下的点颜色种数小于k,最大化水平线之下的点数。

2 <= k <= n <= 200000,横纵坐标 ∈ [1, 10^9]。

题解:

由于是某条水平线(L<=x<=R, y = t)之下的点,可以考虑不包含颜色m的所有点,那么这些点要么纵坐标大于t,要么横坐标小于L或大于R,那么这个区域的答案可以留在刚好卡在水平线之上纵坐标最小的那个颜色为m的点A时来考虑,同理左右边界也可以这样扩大,即纵坐标小于等于t的点里,颜色为m的,横坐标一个在A左边,一个在A右边,离得最近的两个点,可以作为左右边界。

发现每次固定的是一个高点,查询的是纵坐标小于等于一个定值的区域里的信息,于是我们可以扫描线从下往上来处理,涉及到不同颜色的信息,我们可以用k个平衡树来维护。对于每个有点的纵坐标,首先枚举这个坐标上的每个点,查询相应颜色的左右边界,算这块区域里的点数,这个可以按照横坐标利用树状数组来维护,一条线上的所有点都枚举完之后,再把他们的信息加入树状数组和平衡树里。

方便树状数组统计信息,需要先将横坐标离散化成O(n)个数字,对于上边界无限制的情况,可以在所有点都加完之后,枚举不选的颜色和相应的左右边界来统计。总时间复杂度O(nlogn)。

代码:

#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 200001;
int t, n, m, tot, que[maxn], bit[maxn], ans;
set<int> pp[maxn];
struct Point
{
	int x, y, c;
	bool operator < (const Point &t) const
	{
		return y < t.y;
	}
} p[maxn];
void upd(int &x, int y)
{
	if(x < y)
		x = y;
}
void add(int x, int v)
{
	for( ; x <= tot; x += x & -x)
		bit[x] += v;
}
int sum(int x)
{
	int ret = 0;
	for( ; x > 0; x -= x & -x)
		ret += bit[x];
	return ret;
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		tot = ans = 0;
		memset(bit, 0, sizeof bit);
		scanf("%d%d", &n, &m);
		for(int i = 0; i < n; ++i)
		{
			scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].c);
			--p[i].c;
			que[tot++] = p[i].x;
		}
		sort(que, que + tot);
		tot = unique(que, que + tot) - que;
		for(int i = 0; i < n; ++i)
			p[i].x = lower_bound(que, que + tot, p[i].x) - que + 1;
		sort(p, p + n);
		for(int i = 0; i < m; ++i)
		{
			pp[i].clear();
			pp[i].insert(0);
			pp[i].insert(tot + 1);
		}
		for(int i = 0, j; i < n; )
		{
			for(j = i; j < n && p[i].y == p[j].y; ++j)
			{
				set<int>::iterator it = pp[p[j].c].lower_bound(p[j].x);
				int R = *it - 1, L = *(--it);
				upd(ans, sum(R) - sum(L));
			}
			for( ; i < j; ++i)
			{
				add(p[i].x, 1);
				pp[p[i].c].insert(p[i].x);
			}
		}
		for(int i = 0; i < m; ++i)
		{
			int last = 0, now;
			for(set<int>::iterator it = ++pp[i].begin(); it != pp[i].end(); ++it)
			{
				now = *it;
				upd(ans, sum(now - 1) - sum(last));
				last = now;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

H. Darts

题意:

考虑一个扔飞镖的游戏。板子由十个环组成,半径分别为20, 40, 60, 80, 100, 120, 140, 160, 180和200(单位:mm),均以原点为中心。每次投掷的得分取决于飞镖所击中的位置。如果包含飞镖的最小环(可以在圆上)的半径是20 * (11 - p),则得分是p。不在最外环以内的点不得分。你的任务是计算n次投掷之后的得分。

n <= 10^6。

题解:

看懂题然后模拟就好啦。

代码:

#include <cctype>
#include <cstdio>
template<class T> void scan(T& x)
{
	static int ch, flag;
	while(!isdigit(ch = getchar()) && ch != '-');
	x = (flag = ch == '-') ? 0 : ch - '0';
	while(isdigit(ch = getchar()))
		x = (x << 3) + (x << 1) + ch - '0';
	if(flag)
		x = -x;
}int t, n, x, y, ans;
int main()
{
	scan(t);
	while(t--)
	{
		ans = 0;
		scan(n);
		while(n--)
		{
			scan(x), scan(y);
			for(int i = 1; i <= 10; ++i)
				if(x * x + y * y <= 400 * i * i)
				{
					ans += 11 - i;
					break;
				}
		}
		printf("%d\n", ans);
	}
	return 0;
}

I. The Dragon and the knights

题意:

平面上有n条直线,m个点,直线划分出了一些区域,问是否每个区域都至少有一个点。保证任意两点互异,任意两线不重合,任意点不在线上,任意三线不共点。

n <= 100, m <= 50000。

题解:

每个区域要么在第i条直线的上方,要么在第i条直线的下方,所以每个区域可以用一个长度为n的二进制串唯一表示。

算出有多少个区域,如果在不同区域里的点数等于区域数则每个区域都至少有一个点,时间复杂度O(n^2+nm+mlogm)。

代码:

#include <queue>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
template<class T> void scan(T& x)
{
	int ch, flag;
	while(!isdigit(ch = getchar()) && ch != '-');
	x = (flag = ch == '-') ? 0 : ch - '0';
	while(isdigit(ch = getchar()))
		x = (x << 3) + (x << 1) + ch - '0';
	if(flag)
		x = -x;
}
const int maxn = 101, maxm = 50001;
int t, n, m, a[maxn], b[maxn], c[maxn], cnt;
pair<LL, LL> h[maxm];
int main()
{
	scan(t);
	while(t--)
	{
		scan(n), scan(m);
		cnt = n + 1;
		for(int i = 0; i < n; ++i)
		{
			scan(a[i]), scan(b[i]), scan(c[i]);
			for(int j = 0; j < i; ++j)
				cnt += a[i] * b[j] != b[i] * a[j];
		}
		for(int i = 0; i < m; ++i)
		{
			int x, y;
			scan(x), scan(y);
			h[i] = {0, 0};
			for(int j = 0; j < n; ++j)
				if((LL)a[j] * x + (LL)b[j] * y + c[j] > 0)
				{
					if(j < 50)
						h[i].first |= 1LL << j;
					else
						h[i].second |= 1LL << j - 50;
				}
		}
		sort(h, h + m);
		cnt -= unique(h, h + m) - h;
		puts(cnt ? "VULNERABLE" : "PROTECTED");
	}
	return 0;
}

J. Conservation

题意:

有两个实验室,有n个事件需要在特定的实验室里完成,有m条约束关系,关系(A,B)被表示为如果想完成B,需要先完成A,问最少要在两个实验室之间移动多少次可以完成所有的事件,保证约束关系不成环。

n <= 10^5, m <= 10^6。

题解:

题目即拓扑排序,显然可以贪心来做,枚举开始在哪个实验室,能完成的事件都尽量完成,不能完成则换实验室,每次的操作即删除某个入度为零的点,一些点的入度减一,加入一些入度为0的点,时间复杂度O(n + m)。

代码:

#include <cstdio>
#include <cstring>
const int maxn = 100001, maxm = 1000001;
int n, m, typ[maxn], tot, pre[maxn], deg[maxn], que[2][maxn], L[2], R[2];
struct Edge
{
	int nxt, v;
} e[maxm];
int calc(int now)
{
	int ret = 0;
	for(int it = 1; it <= tot; ++it)
		++deg[e[it].v];
	L[0] = L[1] = R[0] = R[1] = 0;
	for(int i = 0; i < n; ++i)
		if(!deg[i])
			que[typ[i]][R[typ[i]]++] = i;
	for(int i = now; ; i ^= 1, ++ret)
	{
		while(L[i] < R[i])
		{
			int &u = que[i][L[i]++];
			for(int it = pre[u]; it; it = e[it].nxt)
				if(!(--deg[e[it].v]))
					que[typ[e[it].v]][R[typ[e[it].v]]++] = e[it].v;
		}
		if(L[0] == R[0] && L[1] == R[1])
			break;
	}
	return ret;
}
int min(int x, int y)
{
	return x < y ? x : y;
}
int main()
{
	int t;
	scanf("%d", &t);
	while(t--)
	{
		tot = 0;
		memset(pre, 0, n * sizeof(int));
		scanf("%d%d", &n, &m);
		for(int i = 0; i < n; ++i)
		{
			scanf("%d", typ + i);
			--typ[i];
		}
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			e[++tot] = (Edge){pre[--u], --v};
			pre[u] = tot;
		}
		printf("%d\n", min(calc(0), calc(1)));
	}
	return 0;
}

K. Graphic Madness

题意:

给定一颗n+k个点的树和一颗m+k个点的树,保证它们都恰好有k个叶子节点,然后用k条边把这k个点连起来,问这个图里是否存在一个哈密顿回路恰好经过每个点一次,若有则输出一组解。

2 <= k <= 1000,1 <= n, m <= 1000。

题解:

类似于构造的题目,根据图论知识我们可以发现,由于k >= 2,那么在两颗树之间的边都非常重要,一定会出现在哈密顿回路上,如果存在解的话,必须要走叶子和叶子之间的边,这样的路径必然是在某个公共祖先节点拐弯,其他点上下直行的,那么每个点最多会选两条相连的边作为答案里的边。

除了叶子节点之外的父亲节点可以通过贪心思想来考虑它在回路里的贡献:

如果当前节点与2个叶子相连,则要上来通过这个点再下去,不然两个叶子节点都废了,另外这个点的父亲也不需要考虑从它向上的情况了,于是将它的贡献不算给父亲,并且炸掉它和父亲的边

如果与1个叶子相连,那它是途经的点,贡献照常给父亲,继续向上走即可。

如果与0个或超过2个叶子相连,那就真的无解了。

按照这样处理后可以保证一颗树里不包含不合法的边,但是这是两颗树合在一起,检查合法还需做一遍dfs,看剩下的边是否组成一个环,这样也便于输出。

时间复杂度O(n+m+k)。

代码:

#include <cstdio>
#include <cstring>
const int maxn = 1010;
int t, n, m, k;
int idx(char* s)
{
	int x;
	if(sscanf(s + 2, "%d", &x) == EOF)
		return -1;
	if(strncmp(s, "AP", 2) == 0)
		return x;
	if(strncmp(s, "AS", 2) == 0)
		return x + n;
	if(strncmp(s, "BP", 2) == 0)
		return x + n + k;
	if(strncmp(s, "BS", 2) == 0)
		return x + n + k + m;
	return -1;
}
char out[10];
void idx(int x)
{
	if(x <= 0)
		sprintf(out, "error! index < 0!");
	else if(x <= n)
		sprintf(out, "AP%d", x);
	else if(x <= n + k)
		sprintf(out, "AS%d", x - n);
	else if(x <= n + k + m)
		sprintf(out, "BP%d", x - n - k);
	else if(x <= n + k + m + k)
		sprintf(out, "BS%d", x - n - k - m);
	else
		sprintf(out, "error! index > n + k + m + k");
}
struct Edge
{
	int nxt, v, f;
} e[maxn << 4];
int tot, pre[maxn << 2], ans, que[maxn << 2];
bool leaf[maxn << 2], vis[maxn << 2];
void addedge(int u, int v)
{
	e[tot] = (Edge){pre[u], v, 0};
	pre[u] = tot++;
	e[tot] = (Edge){pre[v], u, 0};
	pre[v] = tot++;
}
bool dfs(int u, int p)//tree
{
	int cnt = 0;
	for(int it = pre[u]; it != -1; it = e[it].nxt)
	{
		if(it == (p ^ 1))
			continue;
		int &v = e[it].v;
		if(!dfs(v, it))
			return 0;
		cnt += !e[it].f;
	}
	if(cnt == 2 && p != -1)
		e[p].f = e[p ^ 1].f = 1;
	return leaf[u] || cnt == 1 || cnt == 2;
}
bool pfs(int u)//circle
{
	vis[u] = 1;
	que[ans++] = u;
	int cnt = 0;
	for(int it = pre[u]; it != -1; it = e[it].nxt)
	{
		int &v = e[it].v;
		if(e[it].f)
			continue;
		++cnt;
		if(!vis[v] && !pfs(v))
			return 0;
	}
	return cnt == 2;
}
int main()
{
	char x[10], y[10];
	scanf("%d", &t);
	while(t--)
	{
		bool flag = 1;
		tot = ans = 0;
		memset(vis, 0, sizeof vis);
		memset(pre, -1, sizeof pre);
		memset(leaf, 0, sizeof leaf);
		scanf("%d%d%d", &k, &n, &m);
		for(int i = 1; i <= k; ++i)
			leaf[n + i] = leaf[n + k + m + i] = 1;
		for(int i = 1; i < n + k; ++i)
		{
			scanf("%s%s", x, y);
			addedge(idx(x), idx(y));
		}
		flag &= dfs(1, -1);
		for(int i = 1; i < m + k; ++i)
		{
			scanf("%s%s", x, y);
			addedge(idx(x), idx(y));
		}
		if(flag)
			flag &= dfs(n + k + 1, -1);
		for(int i = 0; i < k; ++i)
		{
			scanf("%s%s", x, y);
			addedge(idx(x), idx(y));
		}
		if(flag)
			flag &= pfs(1);
		if(flag && ans == n + k + m + k)
		{
			printf("YES");
			for(int i = 0; i < ans; ++i)
			{
				idx(que[i]);
				printf(" %s", out);
			}
			putchar('\n');
		}
		else
			puts("NO");
	}
	return 0;
}

小结

模拟时没有做出BDFGK,其中B我没找对规律,D队友尝试了各种奇怪的方法,F太长没看,G由于我觉得不会做结果队长跟着弃疗啦,K想了各种平方立方的方法最终还是在学了离散数学之后立马想出正解,这一场题目还是挺有趣的,思想上收获挺多。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值