C. Except and Min
注意到 \(K \leqslant 5\),只需检测序列 \(A\) 的前 \(6\) 小的数即可
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n, q;
cin >> n >> q;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<int> is(n);
rep(i, n) is[i] = i;
sort(is.begin(), is.end(), [&](int i, int j) {
return a[i] < a[j];
});
const int INF = 1001001001;
rep(qi, q) {
int k;
cin >> k;
vector<int> b(k);
rep(i, k) cin >> b[i], b[i]--;
int ans = INF;
rep(i, 6) {
bool ng = false;
for (int j : b) if (is[i] == j) ng = true;
if (!ng) { ans = min(ans, a[is[i]]); break; }
}
cout << ans << '\n';
}
return 0;
}
D. Integer-duplicated Path
树上 \(\text{dfs}\)
对于每一条路径,维护路径上所有数的出现次数,以及至少出现 \(2\) 次的不同数的个数
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<vector<int>> to(n);
rep(i, n-1) {
int u, v;
cin >> u >> v;
--u; --v;
to[u].push_back(v);
to[v].push_back(u);
}
vector<bool> ans(n);
map<int, int> cnt; int dupCnt = 0;
auto add = [&](int x) {
cnt[x]++;
if (cnt[x] == 2) dupCnt++;
};
auto del = [&](int x) {
if (cnt[x] == 2) dupCnt--;
cnt[x]--;
};
auto dfs = [&](auto& f, int v, int p=-1) -> void {
add(a[v]);
ans[v] = dupCnt > 0;
for (int u : to[v]) {
if (u == p) continue;
f(f, u, v);
}
del(a[v]);
};
dfs(dfs, 0);
rep(i, n) if (ans[i]) puts("Yes"); else puts("No");
return 0;
}
E. Simple Division
记 \(P=10007\)
令 \(B=MP\)
把 \(N\) 写成 \(N=tB+b\),其中 \(0 \leqslant b < B\),即 \(b = N \bmod B\) 。
则
\[\lfloor\frac{N}{M}\rfloor = tP + \lfloor\frac{b}{M}\rfloor
\]
两边分别对 \(P\) 取模得
\[\lfloor\frac{N}{M}\rfloor \bmod P = \lfloor\frac{b}{M}\rfloor \bmod P
\]
于是我们只需计算 \(b=N \bmod B\) 即可。
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using P = pair<int, int>;
using mint = modint;
const int p = 10007;
int main() {
int k, m;
cin >> k >> m;
vector<P> cl;
rep(i, k) {
int c, l;
cin >> c >> l;
cl.emplace_back(c, l);
}
auto pow = [&](mint x, int l) {
mint res = 1;
while (l) {
if (l&1) res *= x;
x *= x;
l >>= 1;
}
return res;
};
auto repunit = [&](int l) {
mint res = 0, x = 1, ten = 10;
while (l) {
if (l&1) res = res*ten + x;
x = x*ten + x;
ten *= ten;
l >>= 1;
}
return res;
};
auto f = [&]() {
mint res = 0;
for (auto [c, l] : cl) {
res = res*pow(10, l) + repunit(l)*c;
}
return res;
};
mint::set_mod(m*p);
ll n = f().val();
mint::set_mod(p);
mint ans = mint(n/m);
cout << ans.val() << '\n';
return 0;
}
F. Authentic Traveling Salesman Problem
核心思路:分块排序(Zigzag Sorting)
由于坐标范围很大(\(2 \times 10^7\)),而总距离限制相对较宽松,最简单且有效的方法是使用类似于 莫队 的分块排序思想:
- 将平面划分为若干个水平分块(Strips):我们将 \(Y\) 轴方向划分为 \(B\) 个块。
- 块内排序:
- 对于位于同一个块内的点,按照 \(X\) 坐标进行排序。
- 蛇形走位(Zigzag):为了减小从一个块跨越到下一个块时的突跳距离,我们在偶数块按 \(X\) 升序排,在奇数块按 \(X\) 降序排。
- 确定起点:题目要求从位置 1 开始。我们可以先对所有点排序,然后找到位置 1 在排序后序列中的位置,旋转序列使其以 1 开头。
为什么这个方法有效?
假设我们将平面分为 \(\sqrt{N}\) 个块。
- 在块内移动时,\(X\) 方向的总位移约为 \(X_{max}\),一共有 \(\sqrt{N}\) 个块,总计 \(\sqrt{N} \cdot X_{max}\)。
- 在 \(Y\) 方向,每个点平均移动 \(Y_{max} / \sqrt{N}\),总计 \(N \cdot (Y_{max} / \sqrt{N}) = \sqrt{N} \cdot Y_{max}\)。
- 代入数据:\(245 \times 2 \cdot 10^7 + 245 \times 2 \cdot 10^7 \approx 9.8 \times 10^9\)。这个值刚好在 \(10^{10}\) 的限制之内。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int q;
cin >> q;
vector<int> x(q), y(q);
rep(i, q) cin >> x[i] >> y[i];
const int n = 2e7;
int D = max<int>(1, n/sqrt(q));
vector<int> blockId(q);
rep(i, q) blockId[i] = x[i]/D;
vector<int> is(q);
rep(i, q) is[i] = i;
sort(is.begin(), is.end(), [&](int i, int j) -> bool {
if (blockId[i] != blockId[j]) return blockId[i] < blockId[j];
return (y[i] < y[j]) ^ (blockId[i]%2);
});
rep(i, q) {
if (is[i] == 0) {
rotate(is.begin(), is.begin()+i, is.end());
}
}
rep(i, q) cout << is[i]+1 << " \n"[i == q-1];
return 0;
}
浙公网安备 33010602011771号