VP Educational Codeforces Round 18
A. New Bus Route
题意:给你\(n\)个数,其差的绝对值最小的数对就有几个。
答案一定在排序后相邻的两个数里。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::sort(a.begin(), a.end());
int ans = 2e9, cnt = 0;
for (int i = 1; i < n; ++ i) {
if (a[i] - a[i - 1] < ans) {
ans = a[i] - a[i - 1];
cnt = 1;
} else if (a[i] - a[i - 1] == ans) {
++ cnt;
}
}
std::cout << ans << " " << cnt << "\n";
}
B. Counting-out Rhyme
题意:每次以一个人为起点,在环里走\(a_i\)下把数到的那个人删掉,然后下一个人变成起点。求被删掉的人。
模拟题,\(a_i\)大于等于\(n\)的时候取模就行了,因为每走\(n\)次就走回来了。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(k);
for (int i = 0; i < k; ++ i) {
std::cin >> a[i];
}
std::vector<int> b(n);
std::iota(b.begin(), b.end(), 0);
for (int i = 0; i < k; ++ i, -- n) {
int p = a[i] % n;
std::cout << b[p] + 1 << " \n"[i == k - 1];
std::vector<int> c(b.begin() + p + 1, b.end());
for (int j = 0; j < p; ++ j) {
c.push_back(b[j]);
}
b = c;
}
}
C. Divide by Three
题意:给你一个大数,你要删去一些数使得这个数没有前导零且是3的倍数。
3的倍数就是每一位加起来是3的倍数。
那么我们考虑\(dp\),\(f[i][j]\)表示到\(i\)位模\(3\)等于\(j\)最少要删掉几个。转移就是枚举这个数要不要删。注意如果这一位是0且转移过来的把前面都删了就不要转移。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
const int inf = 1e9;
std::vector f(n + 1, std::vector<int>(3, inf));
std::vector pre(n + 1, std::vector<int>(3, -1));
f[0][0] = 0;
auto change = [&](int i, int j, int x, int y, int v) -> void {
if (f[i][j] + v < f[x][y]) {
f[x][y] = f[i][j] + v;
pre[x][y] = j;
}
};
for (int i = 0; i < n; ++ i) {
int d = s[i] - '0';
for (int j = 0; j < 3; ++ j) {
change(i, j, i + 1, j, 1);
if (d == 0 && f[i][j] == i) {
continue;
}
change(i, j, i + 1, (j + d) % 3, 0);
}
}
if (f[n][0] >= n) {
if (s.find('0') != s.npos) {
std::cout << "0\n";
} else {
std::cout << -1 << "\n";
}
return;
}
std::string ans;
for (int i = n, j = 0; i ; -- i) {
if (f[i][j] == f[i - 1][pre[i][j]]) {
ans += s[i - 1];
}
j = pre[i][j];
}
while (ans.size() > 1 && ans.back() == '0') {
ans.pop_back();
}
if (ans.empty()) {
std::cout << -1 << "\n";
return;
}
std::reverse(ans.begin(), ans.end());
std::cout << ans << "\n";
}
D. Paths in a Complete Binary Tree
题意:一棵满二叉树,每个节点的编号就是这个点在中序遍历时的时间戳。给出一个操作序列,从某个位置往上往左往右走,求最后到哪个点。
找规律题。首先发现每一层数的编号是一个等差序列,第\(i\)层最小的点是\(2^{n-i}\),公差为\(2^{n-i+1}\),其中\(n\)是总层数。那么这一层的数只需要求自己的\(lowbit\)就能求出这一层最小的数,因为这一层的数是若干个\(2^{n-i+1}\)加上一个\(2^{n-i}\),容易发现\(n-i\)位一定是1。然后上下就可以根据层来讨论,先看是左节点还是右节点,左节点往上是加\(2^{n-i-1}\),右节点往上是减\(2^{n-i-1}\),往下讨论同理。于是模拟就行。
点击查看代码
void solve() {
i64 n, q;
std::cin >> n >> q;
while (q -- ) {
i64 x;
std::cin >> x;
i64 y = x & -x;
std::string s;
std::cin >> s;
for (auto & c : s) {
if (c == 'U') {
if (y * 2 == n + 1) {
continue;
} else {
if ((x - y) / (y * 2) % 2 == 0) {
x += y;
} else {
x -= y;
}
y *= 2;
}
} else if (c == 'L') {
if (x & 1) {
continue;
} else {
x -= y / 2;
y /= 2;
}
} else {
if (x & 1) {
continue;
} else {
x += y / 2;
y /= 2;
}
}
}
std::cout << x << "\n";
}
}
E. Colored Balls
题意:\(n\)个数,你要选一个\(x\)把每个数分成若干个\(x\)和\(x+1\),求所有数分成的部分总数最少。
如果一个\(x\)是合法的,则对于每个\(i\)都满足\(a_i \% x \leq \lfloor \frac{a_i}{x} \rfloor\),这表示我把\(a_i\)先\(x\)个分组,然后多出来\(a_i \% x\)个,这些就要放到\(x\)里变成\(x+1\),所以多出来的不能大于\(x\)的个数。
假设\(a_1\)是最小的,那么\(x\)肯定小于等于\(a_1\),那么我们可以枚举\(a_1\)被分成了几组,然后检查合不合法,那么如果\(a_i\)分成\(k\),则有\(x = \lfloor \frac{a_i}{k} \rfloor\),检查是不是合法的,然后更新答案就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::sort(a.begin(), a.end());
const i64 inf = 1e18;
auto check = [&](int k) -> i64 {
if (k <= 0) {
return inf;
}
i64 sum = 0;
for (int i = 0; i < n; ++ i) {
if (a[i] % k > a[i] / k) {
return inf;
}
sum += (a[i] + k) / (k + 1);
}
return sum;
};
i64 ans = inf;
for (int i = 1; i <= a[0] / i; ++ i) {
ans = std::min({ans, check(i), check(i - 1)});
ans = std::min({ans, check(a[0] / i), check(a[0] / i - 1)});
}
std::cout << ans << "\n";
}