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\)个点组成的所有锐角三角形的面积和