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] << "\n";
				flag = 1;
				break;
			}
		}
		if (!flag) cout << 0 << "\n";
	}
	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] << " \n"[i == sze - 1];
	for (int i = 1; i <= n; ++i)
		cout << num[i] << "\n";
	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\n1 1\n2 1");
			continue;
		}
		*a = 0;
		for (int i = 50; i >= 1; --i) {
			if (m >= f[i]) {
				a[++*a] = i;
				m -= f[i];
			}
		}
		printf("%d\n", *a + 1);
		for (int i = 1; i <= *a; ++i) {
			printf("%d %d\n", 2 * i - 1, 200 - a[i]);
		}
		printf("%d %d\n", 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 << "\033[32;1m" << #x << " -> "; err(x); } while (0) 
void err() { cout << "\033[39;0m" << endl; } 
template <class T, class... Ts> void err(const T& arg, const Ts&... args) { cout << arg << ' '; err(args...); }
using ll = long long;
using ull = unsigned long long;
const int N = 5e4 + 10, M = 2e5 + 10;
struct Bitset {
    #define W (64)

    int n;
    ull bits[N / W + 10];
    int num[N / W + 10];

    void preWork() {
        for(int i = 0;i <= n / W; ++i) num[i] = __builtin_popcountll(bits[i]);
        for(int i = n / W - 1;i >= 0; --i) num[i] += num[i + 1];
     //  for(int i = 0;i < m;i ++) printf("%d ",sum[i]); puts("");
      //  for(int i = 0;i <= n/W;i ++) printf("%llu ",bits[i]); puts("");
    }

    int ask(int x) {
        if(x > n) return 0; 
        int blockid = x / W;
        int ans = __builtin_popcountll(bits[blockid]>>(x%W));
        blockid++;
        if(blockid <= n/W) ans += num[blockid];
        return ans;
    }

    int ask(int l,int r) {
		if (l > r) return 0;
        return ask(l) - ask(r+1);
    }

	void Xor(const Bitset &t) {
		for (int i = 0; i <= n / W; ++i) bits[i] ^= t.bits[i]; 
	}

	void And(const Bitset &t) {
		for (int i = 0; i <= n / W; ++i) bits[i] &= t.bits[i];
	}

    void Or(const Bitset &t) {
        for(int i = 0; i <= n / W; ++i) bits[i] |= t.bits[i];
    }

    void Copy(const Bitset &t) {
        n = t.n;
        for(int i = 0; i <= n / W; ++i) bits[i] = t.bits[i];
    }

    void Set(int x) { 
        bits[x / W] |= 1llu << (x % W);
    }

	void Reset(int x) {
		Set(x);
		bits[x / W] ^= 1llu << (x % W);
	}

    void init(int _n) {
        n = _n + 1; //n++;
        for(int i = 0; i <= n / W; i++) bits[i] = 0;
    }

    void print() {
        for(int i = 0; i <= n; ++i) { 
            if(bits[i / W] >> (i % W) & 1) 
                printf("%d ", i);
        }
        puts("");
    }

    #undef W
}bs[N], bg[N];
int n, m, q, f[N];
vector <vector<int>> G; 
struct Tarjan {
	int Low[N], DFN[N], sta[N], Belong[N], num[N], d[N], scc; 
	bool Insta[N];
	void dfs(int u) {
		Low[u] = DFN[u] = ++*Low;
		sta[++*sta] = u;
		Insta[u] = 1;
		for (auto &v : G[u]) {
			if (!DFN[v]) {
				dfs(v);
				Low[u] = min(Low[u], Low[v]);
			} else if (Insta[v]) {
				Low[u] = min(Low[u], DFN[v]);
			}
		}
		if (Low[u] == DFN[u]) {
			++scc;
			int v;
			do {
				v = sta[(*sta)--];
				Insta[v] = 0;
				Belong[v] = scc;
				++num[scc];
			} while (v != u);
		}
	}
	void gao() {
		memset(DFN, 0, sizeof DFN);
		memset(Insta, 0, sizeof Insta);
		memset(num, 0, sizeof num);
		memset(d, 0, sizeof d);
		scc = *sta = *Low = 0;
		for (int i = 1; i <= n; ++i) if (!DFN[i]) dfs(i);	
		vector <vector<int>> H(scc + 1), bk(scc + 1);
		for (int i = 1; i <= scc; ++i) bg[i].init(n); 
		for (int u = 1; u <= n; ++u) {
			bk[Belong[u]].push_back(u);
			for (auto &v : G[u]) {
				if (Belong[u] == Belong[v]) {
					continue;
				}
				H[Belong[v]].push_back(Belong[u]);
				++d[Belong[u]];
			}
		}
		queue <int> que;
		for (int i = 1; i <= scc; ++i) if (!d[i]) que.push(i);
		while (!que.empty()) {
			int u = que.front(); que.pop();
			for (auto &it : bk[u]) {
				bg[u].Set(it); 
			}
			for (auto &v : H[u]) {
				bg[v].Or(bg[u]); 
				if (--d[v] == 0) {
					que.push(v);
				}
			}
		}
		for (int i = 1; i <= n; ++i) {
			bs[i] = bg[Belong[i]]; 
			bs[i].Set(i);
			bs[i].preWork();
		}
	}
}tarjan;

int main() {
	int _T; scanf("%d", &_T);
	while (_T--) {
		scanf("%d%d%d", &n, &m, &q);
		G.clear(); G.resize(n + 1); 
		for (int i = 1, u, v; i <= m; ++i) {
			scanf("%d%d", &u, &v);
			G[u].push_back(v);
		}
		tarjan.gao();
	//	for (int i = 1; i <= n; ++i) bs[i].print();
		int len = 1, cnt = 1;
		while (len < n * 2) len <<= 1, ++cnt;  
		while (q--) {
			ll K; int res = 0; scanf("%lld", &K); --K; 
			memset(f, 0, sizeof f); 
			for (int i = cnt; i >= 0; --i) {
				//试着放1 
				ll num = 0;
				int bit = 1 << i; 
				for (int j = 1; j <= n; ++j) {
					if (((j >> i) & 1) == 0) { 
						f[j] |= bit; 
					}
					num += bs[j].ask(f[j], min(n, (f[j] | (bit - 1)))); 
					if (((j >> i) & 1) == 0) {
						f[j] ^= bit;
					}
				}
				if (K >= num) {
					K -= num; 
					for (int j = 1; j <= n; ++j) {
						if ((j >> i) & 1) {
							f[j] |= bit;
						}
					}
				} else {
					res |= bit; 
					for (int j = 1; j <= n; ++j) {
						if (((j >> i) & 1) == 0) {
							f[j] |= bit;
						}
					}
				}
			}
			printf("%d\n", res);
		}
	}
	return 0;
}

H - Approximate Matching

题意:
给出一个长度为\(n\)的模式串,询问你有多少个长度为\(m\)的文本串,使得模式串可以在文本串中被匹配上。
匹配过程中可以有一位是失配的。

思路:
考虑将模式串拆成\(n\)个不同的模式串,那么就转化成了完全匹配。
\(n\)个模式串插入\(AC\)自动机,然后考虑\(f[i][j]\)表示到了文本串的第\(i\)位,并且匹配指针到了\(AC\)自动机上的第\(j\)个结点的方案数。
一旦匹配上了,那么后面的字符任意放直接算贡献,并且这个方案不需要转移给下一位。
也就是说我们把贡献算在第一次匹配的位置。

或者可以将所有没有匹配上的结点进行\(dp\),这样最后算出来的是不合法的方案数,拿总方案减去即可。

代码:

view code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e4 + 10, ALP = 2;
int n, m; char s[N];
ll f[50][N];
struct ACAM {
	struct node {
		int nx[ALP], fail;
		int cnt; 
		node() {
			memset(nx, -1, sizeof nx);
			cnt = 0; 
		}
	}t[N];
	int root, tot;
	int que[N], ql, qr;
	int newnode() {
		++tot;
		t[tot] = node();
		return tot;
	}
	void init() {
		tot = 0;
		root = newnode();
	}
	void insert(char *s) {
		int len = strlen(s);
		int now = root;
		for (int i = 0; i < len; ++i) {
			if (t[now].nx[s[i] - '0'] == -1) 
				t[now].nx[s[i] - '0'] = newnode();
			now = t[now].nx[s[i] - '0'];
		}
		++t[now].cnt;
	}
	void build() {
		ql = 1, qr = 0;
		t[root].fail = root;
		for (int i = 0; i < ALP; ++i) {
			if (t[root].nx[i] == -1) {
				t[root].nx[i] = root;
			} else {
				t[t[root].nx[i]].fail = root;
				que[++qr] = t[root].nx[i];
			}
		}
		while (ql <= qr) {
			int now = que[ql++];
			for (int i = 0; i < ALP; ++i) {
				if (t[now].nx[i] == -1) {
					t[now].nx[i] = t[t[now].fail].nx[i];
				} else {
					t[t[now].nx[i]].fail = t[t[now].fail].nx[i];
					que[++qr] = t[now].nx[i];
				}
			}
		}
	}
	ll gao() {
		ll res = 0;
		for (int i = 0; i <= m; ++i)
			for (int j = 0; j <= tot; ++j)
				f[i][j] = 0;
		f[0][root] = 1;
		for (int i = 0; i < m; ++i) {
			for (int j = 1; j <= tot; ++j) {
				if (t[j].cnt > 0) continue;
				for (int k = 0; k < 2; ++k) {
					if (t[j].nx[k] != -1) { 
						f[i + 1][t[j].nx[k]] += f[i][j];
						continue;
					} 
				}
			}
		}
		for (int i = 1; i <= m; ++i) {
			for (int j = 1; j <= tot; ++j) {
				if (t[j].cnt > 0) {
					res += f[i][j] * (1ll << (m - i));
				}
			}
		}

		return res;
	}
}acam;

int main() {
	int _T; scanf("%d", &_T);
	while (_T--) {
		scanf("%d%d%s", &n, &m, s);
		acam.init(); acam.insert(s);
		for (int i = 0; i < n; ++i) {
			s[i] = ((s[i] - '0') ^ 1) + '0';
			acam.insert(s);
			s[i] = ((s[i] - '0') ^ 1) + '0';
		}
		acam.build();
		printf("%lld\n", acam.gao());
	}
	return 0;
}

I - Palindromes

题意:
找第\(k\)个回文数。\(k\)很大。

思路:
找规律。

代码:

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

const int N = 1e6 + 10;
char s[N];

int main() {
	int _T; scanf("%d", &_T);
	while (_T--) {
		scanf("%s", s + 1);
		int len = strlen(s + 1);
		if (len == 1) {
			printf("%c\n", s[1] - 1);
			continue;
		} 
		if (s[1] > '1') {
			s[1]--;
			printf("%s", s + 1);
			reverse(s + 1, s + len);
			s[len] = 0;
			printf("%s", s + 1);
		} else if (s[2] == '0') {
			for (int i = 1; i < len; ++i)
				s[i] = s[i + 1];
			s[1] = '9';
			s[len] = 0; len--;
			printf("%s", s + 1);
			reverse(s + 1, s + len);
			s[len] = 0;
			printf("%s", s + 1);
		} else {
			for (int i = 1; i < len; ++i)
				s[i] = s[i + 1];
			s[len] = 0; len--;
			printf("%s", s + 1);
			reverse(s + 1, s + len + 1);
			printf("%s", s + 1);
		}
		puts("");
	}
	return 0;
}

J - Rikka with Triangles

题意:
给出\(n\)个点,计算这\(n\)个点组成的所有锐角三角形的面积和

posted @ 2019-10-15 07:50  Dup4  阅读(223)  评论(0编辑  收藏  举报