The 2018 ACM-ICPC Asia Beijing Regional Contest

Contest Info


[Practice Link](https://vjudge.net/contest/334680)
Solved A B C D E F G H I J
6/10 O O - O - Ø - O O -
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A - Jin Yong’s Wukong Ranking List

题意:
给出(n)对有向关系,判断前多少对关系会形成一个环。

思路:
慢慢加入一对关系,跑拓扑排序,出现环就停止。

代码:

view code
#include <bits/stdc++.h>
using namespace std;

const int N = 510;
int n;
map<string, int> mp; int tot;
string s[2][N];
int getid(string s) {
	if (mp.count(s)) return mp[s];
	mp[s] = ++tot;
	return mp[s];
}

vector <vector<int>> G;
int d[N];
bool gao(int n) {
	memset(d, 0, sizeof d);
	G.clear(); G.resize(tot + 1);
	for (int i = 1; i <= n; ++i) {
		int u = getid(s[0][i]), v = getid(s[1][i]);
		++d[v];
		G[u].push_back(v);
	}
	int cnt = 0;
	queue <int> que; 
	for (int i = 1; i <= tot; ++i) if (!d[i]) que.push(i);
	while (!que.empty()) {
		int u = que.front(); que.pop();
		++cnt;
		for (auto &v : G[u]) {
			if (--d[v] == 0) {
				que.push(v);
			}
		}
	}
	return cnt != tot;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	while (cin >> n) {
		mp.clear(); tot = 0;
		for (int i = 1; i <= n; ++i) {
			cin >> s[0][i] >> s[1][i];
			getid(s[0][i]); getid(s[1][i]);
		}
		bool flag = 0;
		for (int i = 1; i <= n; ++i) {
			if (gao(i)) {
				cout << s[0][i] << " " << s[1][i] << "
";
				flag = 1;
				break;
			}
		}
		if (!flag) cout << 0 << "
";
	}
	return 0;
}

B - Heshen's Account Book

题意:
模拟题。给出若干行文本,包含空格、数字、字母。
找出其中所有连续的自然数,但是如果某一行的结尾是数字,并且下一行的开头也是数字,那么这两行的数字视为连在一起的。

思路:
直接将所有行连在一起再判断。
能直接连就直接连,不能直接连中间加一个空格。
不要分类讨论做,很多Case考虑不到。

代码:

view code
#include <bits/stdc++.h>
using namespace std;
using pSI = pair<string, int>;
#define fi first
#define se second
const int N = 2e5 + 10;
int vis[N], num[N], pos, len;
string s, t;
bool isnum(string &s) {
	int len = s.size();
	if (len == 1) {
		return isdigit(s[0]);
	} 
	if (s[0] == '0') return false;
	for (int i = 0; i < len; ++i)
		if (!isdigit(s[i]))
			return false;
	return true;
}

pSI get() {
	pSI tmp = pSI("", -1);
	while (pos < len && t[pos] == ' ') ++pos;
	while (pos < len && t[pos] != ' ') {
		if (tmp.se == -1) tmp.se = vis[pos];
		tmp.fi += t[pos];
		++pos; 
	}
	return tmp;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	memset(vis, 0, sizeof vis);
	memset(num, 0, sizeof num);
	s = t = "";
	int pre = -1; t = "";
	int n = 0;
	while (getline(cin, s)) {
		++n;
		if (!t.empty() && isdigit(t.end()[-1]) && isdigit(s[0])) {
			t += s;
		} else {
			t += " ";
			t += s;
			++pre;
		}
		int len = t.size();
		for (int i = pre + 1; i < len; ++i) vis[i] = n;
	   	pre = len - 1;	
	} 
	len = t.size();
	pos = 0;
	vector <string> vec;
	while (1) {
		pSI tmp = get();
		if (tmp.se == -1) break;
		if (isnum(tmp.fi)) {
			++num[tmp.se];
			vec.push_back(tmp.fi);
		}
	}
	int sze = vec.size();
	for (int i = 0; i < sze; ++i)
		cout << vec[i] << " 
"[i == sze - 1];
	for (int i = 1; i <= n; ++i)
		cout << num[i] << "
";
	return 0;
}

C - Pythagorean triple

题意:
统计有多少个三元组((a, b, c))使得(a^2 + b^2 = c^2),并且满足(c leq N)

D - Frog and Portal

题意:
现在有(201)个点,可以加一些传送门,一旦进入这个点就会被传送到另一个点。
现在青蛙在(0)号点,它要到(200)号点,它如果在(p)号点,那么下一步可以去(p + 1)或者(p + 2)号点。
问你如何加传送门,使得它从(0)(200)的方案数是(m)

思路:
考虑不加任何传送门方案数是第(201)个斐波那契数。
我们可以考虑任意一个整数都可以被分解为若干个斐波那契数相加。
那么我们从起点的某个地方直接传送到(201 - x)那个点,那么从(x)(201)的方案数就是第(x)个斐波那契数。
并且控制从(0)走到那个传送门的方案为(1)即可。

代码:

view code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 110;

ll m;
ll f[N];
int a[N];

int main() {
	f[0] = 1, f[1] = 1;
	for (int i = 2; i <= 50; ++i) {
		f[i] = f[i - 1] + f[i - 2];
	}
	while (scanf("%lld", &m) != EOF) {
		if (m == 0) {
			puts("2
1 1
2 1");
			continue;
		}
		*a = 0;
		for (int i = 50; i >= 1; --i) {
			if (m >= f[i]) {
				a[++*a] = i;
				m -= f[i];
			}
		}
		printf("%d
", *a + 1);
		for (int i = 1; i <= *a; ++i) {
			printf("%d %d
", 2 * i - 1, 200 - a[i]);
		}
		printf("%d %d
", 2 * (*a), 2 * (*a));
	}
	return 0;
}

F - The Kth Largest Value

题意:
给出一个有向图,定义((u, v))是好的二元组当且仅当(u)(v)至少存在一条可达路径,当然((u, u))是好的。
现在定义二元组((u, v))的权值为(u oplus v),现在有(q)次询问,询问所有好的二元组中的第(k)大的权值。

思路:
先用(tarjan + topo)求出拓扑序,并且用(bitset)求出(f[u])表示(u)可以到达哪些点。
显然有个思路是二分,然后去找有多少个(u oplus v > mid),但是这个统计可以放在(Trie)上做,所以就不用二分了。
那么从高位到低位贪心,每次尝试当前位放(0),那么对于当前位放(1)并且低位任意放的情况都是比当前这个数要大的。
放到字典树上就是统计子树和。
但是我们注意到它的权值是([1, n])连续的,所以不用字典树,它是一棵完全二叉树,并且(DFS)序是确定的就是([1, n])
那么相当于对于每个点查询一段区间和。可以用手写(bitset)维护(f[u])的前缀和,就可以(O(1))查询一段区间内(1)的个数。
并且注意对于每个(u)来说,在它的字典树上往下走的过程要异或(u)的那一位二进制位。

代码:

view code
#include <bits/stdc++.h>
using namespace std;
#define dbg(x...) do { cout << "