2019-2020 ICPC Asia Hong Kong Regional Contest
A. Axis of Symmetry
題意:給定一堆邊和坐標軸平行且不重疊的矩形,找出所有對稱軸。
分析:這題猜結論很簡單,對稱軸最多也就四條:兩條垂直坐標軸的,兩條對角線形式的(可以參考題面中樣例的圖片)。為了找出這四條對稱軸,我們只需要找到整個圖形的四條邊界線。然后對所有的點進行 \(check\) ,檢測是否每一個點都有關於對稱軸對稱的點。
這里對點的 \(check\) 有一個需要注意的點:被偶數個矩形覆蓋的點不需要考慮,如下圖(紅色為偶數點,藍色為奇數點):
因為偶數點必定為 \(180°\) 或者 \(360°\) ,奇數點必定是 \(90°\) 或者 \(270°\) ,因此奇數點必定是轉角,而偶數點則落在邊上。算法實現用 \(map\) 存所有的點就行了,還需要注意輸出格式要按字典序輸出 !
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 5005
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int t, n;
bool vis[4];
vector<int> gao(int x, int y, int z) {
vector<int> ans(3);
int g = __gcd(x, __gcd(y, z));
if (x / g < 0) g = -g;
else if (x == 0 && y / g < 0) g = -g;
ans[0] = x / g; ans[1] = y / g; ans[2] = z / g;
return ans;
}
int main() {
io(); cin >> t;
while (t--) {
map<pair<int, int>, int> MP;
cin >> n;
rep(i, 0, 3) vis[i] = true;
int xa, ya, xb, yb;
int mxx = -2e9, mnx = 2e9, mxy = -2e9, mny = 2e9;
rep(i, 1, n) {
cin >> xa >> ya >> xb >> yb;
xa <<= 1; ya <<= 1; xb <<= 1; yb <<= 1;
MP[mp(xa, ya)] ^= 1;
MP[mp(xa, yb)] ^= 1;
MP[mp(xb, ya)] ^= 1;
MP[mp(xb, yb)] ^= 1;
mxx = max(mxx, xb);
mnx = min(mnx, xa);
mxy = max(mxy, yb);
mny = min(mny, ya);
}
int dx = (mxx + mnx) / 2, dy = (mxy + mny) / 2;
for (auto it : MP) {
if (!it.second) continue;
int x = it.first.first, y = it.first.second;
pair<int, int> pr = mp(x, dy - (y - dy));
vis[0] = (vis[0] && MP.count(pr) && MP[pr]);
pr = mp(dx - (x - dx), y);
vis[1] = (vis[1] && MP.count(pr) && MP[pr]);
pr = mp(dx + (y - dy), dy + (x - dx));
vis[2] = (vis[2] && MP.count(pr) && MP[pr]);
pr = mp(dx - (y - dy), dy - (x - dx));
vis[3] = (vis[3] && MP.count(pr) && MP[pr]);
}
vector<vector<int> > res;
if (vis[0]) res.emplace_back(gao(0, 2, dy));
if (vis[1]) res.emplace_back(gao(2, 0, dx));
if (vis[2]) res.emplace_back(gao(2, -2, dx - dy));
if (vis[3]) res.emplace_back(gao(2, 2, dx + dy));
sort(res.begin(), res.end(), [](auto& a, auto& b) {
if (a[0] != b[0]) return a[0] > b[0];
if (a[1] != b[1]) return a[1] > b[1];
return a[2] > b[2];
});
cout << res.size() << '\n';
for (auto it : res) cout << it[0] << ' ' << it[1] << ' ' << it[2] << ' ';
cout << '\n';
}
}
B. Binary Tree
題意:給定一棵樹,兩個人輪流進行游戲,一個人每次能從樹上取走一棵符合滿二叉樹性質的子樹,詢問誰能必勝。
分析:滿二叉樹的節點數必定為奇數,因此決定勝負的是樹上節點數的奇偶性。
#include <bits/stdc++.h>
using namespace std;
int main() {
int t; scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
int u, v;
for (int i = 1; i < n; i++)
scanf("%d%d", &u, &v);
if (n & 1) printf("Alice\n");
else printf("Bob\n");
}
}
C. Constructing Ranches
題意:給定一棵樹,每個節點有一個權值 \(a_i\) ,詢問樹上有多少條路徑滿足:如果把這條路徑經過的所有節點上的權值看作邊長,那這些邊可以構成一個簡單多邊形。
分析:邊集 \(\{a_1,a_2,...,a_n\}\) 能構成簡單多邊形的充要條件是 \(\sum^n_{i=1}a_i>2\underset{1\leq j \leq n}{max}a_j\) ;就是邊集中的最長邊長度必須小於其余所有邊長之和。
剩下的樹分治隊友寫的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e5 + 5;
const int maxm = 2e5 + 5;
int n;
LL ans, a[maxn];
struct Edge {
int v;
int next;
} edge[maxm << 1];
int head[maxn], tot;
void init(int n) {
fill(head, head + 1 + n, 0);
tot = 0;
}
inline void AddEdge(int u, int v) {
edge[++tot].v = v;
edge[tot].next = head[u];
head[u] = tot;
}
int n_size[maxn], dp[maxn], root, sum;
bool vis[maxn];
void GetRoot(int now, int fa) {
n_size[now] = 1;
dp[now] = 0;
for (int i = head[now]; i; i = edge[i].next) {
if (vis[edge[i].v] || edge[i].v == fa)
continue;
GetRoot(edge[i].v, now);
n_size[now] += n_size[edge[i].v];
dp[now] = max(dp[now], n_size[edge[i].v]);
}
dp[now] = max(dp[now], sum - n_size[now]);
if (dp[now] < dp[root])
root = now;
}
LL cal(int, LL);
void solve(int now) {
vis[now] = true;
ans += cal(now, 0);
for (int i = head[now]; i; i = edge[i].next) {
if (vis[edge[i].v])
continue;
ans -= cal(edge[i].v, a[now]);
root = 0;
dp[0] = sum = n_size[edge[i].v];
GetRoot(edge[i].v, 0);
solve(root);
}
}
void doit() {
memset(vis, 0, sizeof(vis));
dp[root = 0] = sum = n;
GetRoot(1, 0);
solve(root);
}
typedef pair<LL, LL> pll;
int cnt;
pll line[maxn], val[maxn];
void dfs_add(int now, LL maxval, LL w, int fa) {
cnt++;
line[cnt] = make_pair(w, cnt);
val[cnt].first = maxval;
for (int i = head[now]; i; i = edge[i].next) {
if (vis[edge[i].v] || edge[i].v == fa)
continue;
dfs_add(edge[i].v, max(maxval, a[edge[i].v]), w + a[edge[i].v], now);
}
}
int tree[maxn];
inline int lowbit(int x) { return x & -x; }
void update(int x, int val) {
for (int i = x; i <= cnt; i += lowbit(i))
tree[i] += val;
}
int query(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i))
res += tree[i];
return res;
}
LL cal(int now, LL w) {
cnt = 0;
dfs_add(now, max(w, a[now]), w + a[now], 0);
LL head = w ? w : val[1].first;
sort(line + 1, line + cnt + 1);
for (int i = 1; i <= cnt; i++)
val[line[i].second].second = i;
sort(val + 1, val + 1 + cnt);
fill(tree, tree + 1 + cnt, 0);
LL res = 0;
int left, right, mid;
LL limit, ret;
for (int i = 1; i <= cnt; i++) {
left = 1, right = cnt;
limit = 2 * val[i].first - line[val[i].second].first + head;
ret = 0;
while (left <= right) {
mid = (left + right) >> 1;
if (line[mid].first <= limit) {
ret = mid;
left = mid + 1;
} else right = mid - 1;
}
res += i - 1;
if (ret) res = res - query(ret);
update(val[i].second, 1);
}
return res;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
init(n);
int u, v;
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
AddEdge(u, v);
AddEdge(v, u);
}
ans = 0;
doit();
printf("%lld\n", ans);
}
}
D. Defining Labels
題意:進制轉換。
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 200005
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int k, t, n;
void dfs(int x) {
if (x > k) dfs((x - 1) / k);
cout << 9 + (x % k == 0 ? k : x % k) - k;
}
int main() {
io(); cin >> t;
rep(i, 1, t) {
cin >> k >> n;
dfs(n);
cout << '\n';
}
}
E. Erasing Numbers
題意:給定一個 \(1,2,...,n\) 的排列 ( \(n\) 為奇數),你每次能從數組中選擇連續的三個數,並刪除這三個數中最大的和最小的(保留中位數),直到最后剩下一個數,詢問該排列中的每一個數字能否成為最后剩下的數字。
分析:這題思路相當妙,首先觀察數據范圍,解法應該是遍歷每一個數字,並 \(O(n)\) 判斷這個數字能否留下;然后需要想到我們每次對某個數字 \(X\) 判斷它能否留下來的關鍵在於最后一步時,它是否是最后三個數字的中位數。我們用 \(0\) 表示比 \(X\) 小的數,用 \(1\) 表示比 \(X\) 大的數,那么最后三個數必須是 \(01X\) 的一種排列。
接下來我們需要觀察得到一個性質:當這個排列中的 \(0,1\) 數量相等時,數字 \(X\) 必定能夠留下,因為 \(0,1\) 的數量相等,所以必定有 \(0,1\) 相鄰的地方,我們對於這個相鄰點進行一次消除,那么不論另外一個數字是什么,我們都能夠保證恰好消除了一個 \(1\) ,一個 \(0\) ,於是最后剩下的只能是 \(01X\) 的一個排列。
因此我們將問題轉化為:能否將排列中的 \(0,1\) 數量變得相等。要使 \(0,1\) 的數量變得相等,只有消除 \(111\) 或者 \(000\) 這樣的情況,因此我們可以 \(O(n)\) 遍歷並模擬這一過程。
再更新一個簡單的正確性證明,已經理解了就不用管了:這道題最巧妙的地方其實是奇偶性,因為 \(n\) 是奇數,所以 \(0,1\) 的數量奇偶性一致,不論我們一次刪除了兩個相同數字還是不同數字,都不會改變 \(0,1\) 數量奇偶的一致性。再結合上面說明的性質,我們只需要讓 \(0,1\) 的數量變成相等的就行了,如果無法變成相等的那么最后必然留下兩個 \(0\) 或者 \(1\) 。
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 5005
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
const int maxn = 2e5;
ll t, n, a[SIZE];
bool check(int pos) {
int dif = abs(a[pos] - (n + 1) / 2);
int f = (a[pos] < (n + 1) / 2);
int now = 0, num = 0;
rep(i, 1, n) {
if (i == pos) { now = 0; continue; }
int ff = (a[i] < a[pos]);
if (f == ff) now = max(now - 1, 0);
else {
++now;
if (now == 3) ++num, now -= 2;
}
}
return num >= dif;
}
int main() {
io(); cin >> t;
while (t--) {
cin >> n;
rep(i, 1, n) cin >> a[i];
rep(i, 1, n) {
if (check(i)) cout << 1;
else cout << 0;
}
cout << '\n';
}
}
G. Game Design
題意:構造一棵節點帶權值的樹。構造完成后,樹上的每個葉子節點都會出現一個怪物,怪物會自底向上進攻根節點,現在你能夠在任意個節點建造防御塔,防御塔的建造花費是你構造是給出的點權 \(c_i\) ,以建造防御塔后的節點為根的子樹中的所有怪物都會被防御塔殺死。現在給定一個正整數 \(K\) 代表以最小花費修建防御塔攔截殺死怪物的不同方案數,要求給出這個構造。
分析:由於 \(K\) 的范圍很大,我們肯定不能 \(O(K)\) 建樹,因此需要遞歸建樹。然后考慮構造的方案,如果當前節點的方案數為偶數,那么我們構造兩個方案數分別為 \(2,\frac{n}{2}\) 的子節點;如果是奇數就構造兩個方案數分別為 \(2,\frac{n}{2}\) 的子節點,並且根節點單獨作為一種方案,遞歸終點是 \(k\leq 2\) 的時候,我們只需要建一條鏈即可。
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 200005
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int k, cnt;
int pa[SIZE], c[SIZE], tmp[SIZE];
int dfs(int k, int f) {
int now = ++cnt;
pa[now] = f;
if (k <= 2) {
pa[++cnt] = now;
c[cnt] = tmp[now] = 1;
c[now] = 3 - k;
return 1;
}
tmp[now] = dfs(k / 2, now) + dfs(2, now);
c[now] = tmp[now] + (k % 2 == 0);
return tmp[now];
}
int main() {
io(); cin >> k;
dfs(k, 0);
cout << cnt << '\n';
rep(i, 2, cnt) cout << pa[i] << ' ';
cout << '\n';
rep(i, 1, cnt) cout << c[i] << ' ';
}
I. Incoming Asteroids
隊友寫的強制在線數據結構?
#include<bits/stdc++.h>
#define ll long long
#define maxn 200010
#define mod 1000000007
using namespace std;
int goal[maxn], num[maxn], q[maxn][3], suc[maxn];
ll sum[maxn], pr[maxn][3];
struct cv {
ll x;
int num;
bool friend operator<(cv p, cv q) {
return p.x > q.x;
}
}hf;
priority_queue<cv>qu[maxn];
vector<int>ans;
void check(int x) {
if (suc[x]) return;
for (int i = 0; i < num[x]; i++) {
goal[x] -= sum[q[x][i]] - pr[x][i];
pr[x][i] = sum[q[x][i]];
}
if (goal[x] <= 0) {
suc[x] = 1;
ans.push_back(x);
return;
}
else {
hf.num = x;
for (int i = 0; i < num[x]; i++) {
hf.x = sum[q[x][i]] + (goal[x] + num[x] - 1) / num[x];
qu[q[x][i]].push(hf);
}
}
}
void add(int x) {
hf.num = x;
for (int i = 0; i < num[x]; i++) {
pr[x][i] = sum[q[x][i]];
hf.x = sum[q[x][i]] + (goal[x] + num[x] - 1) / num[x];
qu[q[x][i]].push(hf);
}
}
int main() {
int n, m, las = 0, cnt = 0;
scanf("%d%d", &n, &m);
while (m--) {
int fl;
scanf("%d", &fl);
if (fl == 1) {
cnt++;
scanf("%d%d", &goal[cnt], &num[cnt]);
goal[cnt] ^= las;
for (int i = 0; i < num[cnt]; i++) {
scanf("%d", &q[cnt][i]);
q[cnt][i] ^= las;
}
add(cnt);
}
else {
int x, y;
scanf("%d%d", &x, &y);
x ^= las, y ^= las;
las = 0;
sum[x] += y;
ans.clear();
while (!qu[x].empty()) {
cv tp = qu[x].top();
if (tp.x > sum[x]) break;
qu[x].pop();
check(tp.num);
}
sort(ans.begin(), ans.end());
las = ans.size();
printf("%d", las);
for (int i = 0; i < las; i++) printf(" %d", ans[i]);
printf("\n");
}
}
return 0;
}
J. Junior Mathematician
隊友寫的數位 \(dp\) 。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 5005;
const int maxm = 65;
const LL mod = 1e9 + 7;
int len, m, a[maxn], base[maxn];
LL dp[maxn][maxm][maxm];
LL dfs(int pos, int pre, int cut, bool limit) {
if (pos > len)
return cut == 0;
if (dp[pos][pre][cut] != -1 && (!limit))
return dp[pos][pre][cut];
LL res = 0;
int top = limit ? a[pos] : 9;
for (int i = 0; i <= top; i++) {
res += dfs(pos + 1, (pre + i) % m, (cut + i * (base[len - pos] - pre + m)) % m, i == top && limit);
if (res >= mod)
res -= mod;
}
if (!limit)
dp[pos][pre][cut] = res;
return res;
}
LL Part(char* s) {
int p = 1;
while (s[p] == '0')
p++;
len = strlen(s + p);
for (int i = 1; i <= len; i++)
a[i] = s[p + i - 1] - '0';
for (int i = 0; i <= len + 1; i++)
for (int j = 0; j <= m; j++)
for (int k = 0; k <= m; k++)
dp[i][j][k] = -1;
return dfs(1, 0, 0, true);
}
char l[maxn], r[maxn];
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%s%s", l + 1, r + 1);
scanf("%d", &m);
int len = strlen(l + 1);
for (int i = len; i >= 1; i--) {
if (l[i] >= '1') {
l[i]--;
break;
} else
l[i] = '9';
}
base[0] = 1;
for (int i = 1; i <= 5000; i++)
base[i] = LL(base[i - 1]) * 10 % m;
int ans = (Part(r) - Part(l) + mod) % mod;
printf("%d\n", ans);
}
return 0;
}