The 2022 ICPC Asia Jinan Regional Contest
A. Tower
首先用了 dp 验证出把一个数字变成另一个数字的最优解一定是能除就先进行除法,然后再使用加一减一。
这样我们就有\(O(\log n)\)的复杂度求出把一个数变成另一个数的最小代价。
然后就是猜测最终的目标一定是某个数除了若干次二得到的。所以就枚举一下目标即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using vi = vector<int>;
void solve() {
int n, m;
cin >> n >> m;
set<int> tot;
vector<int> a(n);
for (int x; auto &i: a) {
cin >> i, x = i;
while (x) tot.insert(x), x /= 2;
}
auto calc = [](int x, int y) {
if (y > x) return y - x;
int cnt = 0;
while (x / 2 > y) cnt++, x /= 2;
return min(cnt + x - y, cnt + 1 + y - x / 2);
};
int res = LLONG_MAX;
for( auto x : tot ){
vi b;
for( auto i : a )
b.push_back( calc( i , x ) );
sort( b.begin(), b.end() );
res = min( res , accumulate(b.begin(), b.end() - m , 0ll) );
}
cout << res << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
for (cin >> T; T; T--)
solve();
return 0;
}
D. Frozen Scoreboard
这题其实就是模拟,首先枚举出那些题是封榜后通过的,先假设题目的都是在 240 分钟时通过,然后先尽可能的使用 WA,如果 WA不能用是再尝试把通过时间向后调。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, m;
using vi = vector<int>;
using vs = vector<string>;
vector<string> state;
vector<int> x, y;
vi unKnow;
int finalSolved, finalPenalty, frozenSolved;
int solved = 0, penalty = 0;
bool flag;
void check(vi v) {
int pPenalty = penalty;
auto sState = state;
auto xX = x, yY = y;
for (int i = 0, j; i < unKnow.size(); i++) {
j = unKnow[i];
if (v[i] == 1) {
sState[j] = "+";
xX[j] = y[j] - x[j] + 1;
yY[j] = 240;
pPenalty += (xX[j] - 1) * 20 + yY[j];
} else {
sState[j] = "-";
xX[j] = y[j];
yY[j] = 0;
}
}
if (pPenalty > finalPenalty)
return;
for (int i = 0, j; i < unKnow.size(); i++) {
if (v[i] == 0) continue;
j = unKnow[i];
while (xX[j] < y[j] and pPenalty + 20 <= finalPenalty) {
pPenalty += 20, xX[j]++;
}
}
for (int i = 0, j, t; i < unKnow.size(); i++) {
if (v[i] == 0) continue;
j = unKnow[i];
t = min(59ll, finalPenalty - pPenalty);
pPenalty += t, yY[j] += t;
}
if (pPenalty != finalPenalty) return;
flag = true;
state = sState, x = xX, y = yY;
return;
}
void dfs(int i, vi v, int cnt) {
if (flag == true) return;
if (cnt == frozenSolved) {
check(v);
return;
}
if (i >= unKnow.size()) return;
dfs(i + 1, v, cnt);
v[i] = 1;
dfs(i + 1, v, cnt + 1);
v[i] = 0;
return;
}
void solve() {
cin >> finalSolved >> finalPenalty;
solved = 0, penalty = 0;
state = vs(m), x = vi(m), y = vi(m);
unKnow = vi();
for (int i = 0; i < m; i++) {
cin >> state[i];
if (state[i] == "+") {
char s;
cin >> x[i] >> s >> y[i];
solved++, penalty += (x[i] - 1) * 20 + y[i];
} else if (state[i] == "?") {
cin >> x[i] >> y[i];
unKnow.push_back(i);
} else if (state[i] == "-") {
cin >> x[i];
}
}
if (solved > finalSolved or penalty > finalPenalty) {
cout << "No\n";
return;
}
if (solved + unKnow.size() < finalSolved) {
cout << "No\n";
return;
}
frozenSolved = finalSolved - solved;
flag = false;
dfs(0, vi(unKnow.size()), 0);
if (flag == false) {
cout << "No\n";
return;
}
cout << "Yes\n";
for (int i = 0; i < m; i++) {
cout << state[i];
if (state[i] == "+") {
cout << " " << x[i] << "/" << y[i];
} else if (state[i] == "-") {
cout << " " << x[i];
}
cout << "\n";
}
return;
}
int32_t main() {
cin >> n >> m;
for (; n; n--)
solve();
return 0;
}
E. Identical Parity
为了方便表示,下述除法都是向下取整
首先可以知道的是如果为偶数时只要一奇一偶的放就好了。
然后考虑奇数的情况,首先数字的值其实无关紧要,只要知道奇偶性即可。所以我们把所有的数字都转换成 01 来表示。所以0的个数有\(\frac n 2\)个,1的个数有\(\frac n 2 + 1\)个。
然后考虑两个相邻的区间,我们可以知道的是每次变化的两个数的奇偶性相同
\[a_1=a_{1+k}=a_{1+2k}=\cdots\\
a_2=a_{2+k}=a_{2+2k}=\cdots\\
\vdots\\
a_k=a_{2k}=a_{3k}=\cdots
\]
所以实际上序列被分成了\(k\)组,其中\(n\%k\)组有\(\frac n k + 1\)个元素,\(n-n\%k\)组有\(\frac n k\)个元素,并且每一组中元素都完全相同。
这样的话实际上我们从两种组面各选多少种全部赋值为 1,只要判断有没有解即可。
\[a=\frac n k + 1 , b = \frac n k , c = \frac n 2 + 1 \\
0\le x \le n\%k, 0\le y\le n - n\%k\\ax+by=c
\]
也就是说判断上述方程是否有解即可。可以先用扩偶解除特解,在根据通解公式判断是否有解。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using vi = vector<int>;
using db = long double;
constexpr int inf = 1E18;
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y;
y = z - y * (a / b);
return d;
}
void solve() {
int n, k;
cin >> n >> k;
if( n == k and n == 1 ){
cout << "Yes\n";
return ;
}
if (k & 1) {
if (k == 1) {
cout << "No\n";
} else {
int a = n / k, b = (n + k - 1) / k, c = n / 2, x0, y0, d;
d = exgcd(a, b, x0, y0);
if (c % d != 0) {
cout << "No\n";
return;
}
int x = c / d * x0, y = c / d * y0, dx = b / d, dy = a / d;
{
int kk = (-x) / dx;
x = x + kk * dx, y = y - kk * dy;
}
for (; y >= 0; x += dx, y -= dy) {
if( x < 0 ) continue;
if (n % k != 0 and x + y <= k and x <= k - n % k and y <= n % k) {
cout << "Yes\n";
return ;
} else if (n % k == 0 and x + y <= k and min(x, y) == 0) {
cout << "Yes\n";
return ;
}
}
cout << "No\n";
}
} else {
cout << "Yes\n";
}
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int TC;
for (cin >> TC; TC; TC--)
solve();
return 0;
}
可惜这样做太慢了,但是我们发现当\(k\)确定时,有解的 n 其实不多。打表可以发现,有解 n 是
k = 3
n/k \ n%k 0 1 2
1 3 4 5
2 7
k=5
n/k \ n%k 0 1 2 3 4
1 5 6 7 8 9
2 11 12 13
3 17
k=7
n/k \ n%k 0 1 2 3 4 5 6
1 7 8 9 10 11 12 13
2 15 16 17 18 19
3 23 24 25
4 31
这样话,实际上可以\(O(1)\)的判断出对于当前的\(k\),\(n\)是否合法
#include <bits/stdc++.h>
using namespace std;
#define int long long
using vi = vector<int>;
constexpr int inf = 1E18;
void solve() {
int n, k;
cin >> n >> k;
if (n == k and n == 1) {
cout << "Yes\n";
return;
}
if (k % 2 == 0) {
cout << "Yes\n";
return;
}
if (k == 1) {
cout << "No\n";
return;
}
int t = n / k;
int l = t * k, r = l + k - 1;
l = l + t - 1, r = r - t + 1;
if (l <= n and n <= r)
cout << "Yes\n";
else
cout << "No\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int TC;
for (cin >> TC; TC; TC--)
solve();
return 0;
}
K. Stack Sort
用 multiset 统计当前栈的结尾是啥
#include <bits/stdc++.h>
using namespace std;
#define int long long
using vi = vector<int>;
void solve() {
int n;
multiset<int> s;
cin >> n;
for (int x; n; n--) {
cin >> x;
if (s.count(x + 1) > 0) {
s.erase(s.lower_bound(x + 1));
}
s.insert(x);
}
cout << s.size() << "\n";
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int TC;
for (cin >> TC; TC; TC--)
solve();
return 0;
}
M. Best Carry Player
高精度模拟一下,统计进位次数就好了
#include <bits/stdc++.h>
using namespace std;
#define int long long
using vi = vector<int>;
void solve() {
int n, len = 0;
cin >> n;
vector<string> num(n);
for (auto &i: num) {
cin >> i;
reverse(i.begin(), i.end());
len = max(len, (int) i.size());
}
vector<int> a(len + 1);
int cnt = 0;
for (int i = 0; i < len; i++) {
for (auto s: num) {
if (i >= s.size()) continue;
a[i] += s[i] - '0';
}
a[i+1] = a[i] / 10 , a[i] %= 10;
cnt += a[i+1];
}
while( a.back() >= 10 )
cnt += a.back() / 10 , a.push_back( a.back() / 10 );
cout << cnt << "\n";
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int TC;
for (cin >> TC; TC; TC--)
solve();
return 0;
}