Codeforces Round 1021 (Div. 2)
A. Vadim's Collection
题意:一个10位数,要求重排使得\(i\in [1, 10], a_i >= 10 - i\)。且字典序最小。
把每个数字出现次数存下来,然后每一位找大于等于它的第一个还没用完的数。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
int cnt[10]{};
for (auto & c : s) {
++ cnt[c - '0'];
}
std::string ans;
for (int i = 9; i >= 0; -- i) {
for (int j = i; j <= 9; ++ j) {
if (cnt[j]) {
ans += (char)(j + '0');
-- cnt[j];
break;
}
}
}
std::cout << ans << "\n";
}
B. Sasha and the Apartment Purchase
评价为难以看懂题目。
题意:\(n\)个点,你要在\([1, 10^9]\)里选点,一个点可以被选那么在删去不超过\(k\)个给定的点情况下,它是和剩下没被删去的给定点的距离和最小的。
先排序。
如果不删除点,那么根据中位数定理选\([a_{\frac{n}{2}}, a_{\lfloor \frac{n}{2} \rfloor}]\)之间的点是符合条件的。
考虑删去\(k\)个,那么肯定是删去两边的点。那么我们可以枚举每个长度为\(n - k\)的区间,奇数来\(n - k + 1\)个区间,然后把题目合并。就可以计算点数。但这样还是太麻烦了,发现这些长度为\(n-k\)的区间,相邻的两个区间的中位数的位置最多移动一位,那么这些区间肯定是相交的。那么直接计算最左边区间的中位数的位置,和最右边的位置就行了。注意最左边的区间要选靠左的中位数,最右边的区间选靠右的中位数。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::ranges::sort(a);
k = n - k;
int l = (k - 1) / 2, r = (n - 1 + (n - 1 - k + 1) + 1) / 2;
std::cout << a[r] - a[l] + 1 << "\n";
}
C. Sports Betting
题意:和\(n\)个人打赌,每一天要么是\(0\)要么是\(1\),和第\(i\)个人在第\(i\)天打赌,告诉他就行了两天的情况。求能不能至少赢一个人。
如果第\(i\)天和两个人打赌,那么可以分别预测是\(01, 10\)这两个,那么第\(i + 1\)天总会有一个符合条件,但只要第\(i + 2\)不是堵的那个就一样数,如果第\(i + 2\)还能和两个人打赌,那么假设第\(i+1\)天是\(0\),那么直接预测\(11, 10\)这两个,那么如果第\(i + 2\)天是\(0\),则赢了第\(i\)天的打赌,否则第\(i+2\)天是\(1\),我们已经预处第\(i+2\)是\(0\)的所有情况了,会赢第\(i+1\)天的打赌。如果第\(i+1\)天只有一个人打赌,那么同样的打赌接下来两天是\(01, 10\)这两个,这样一直下去,只要连续的天数是大于等于\(2\)个人开头,中间若干个\(1\),然后是大于等于\(2\)个人结尾,就能赢。
如果有\(4\)个以上在同一天也是赢。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::map<int, int> mp;
for (auto & x : a) {
++ mp[x];
}
std::vector<std::pair<int, int>> b;
for (auto & [x, y] : mp) {
b.emplace_back(x, y);
}
int m = b.size();
for (int i = 0; i < m; ++ i) {
if (b[i].second >= 4) {
std::cout << "YES\n";
return;
}
}
for (int i = 0; i < m; ++ i) {
if (b[i].second >= 2) {
int j = i;
while (j + 1 < m && b[j + 1].first - 1 == b[j].first) {
++ j;
if (b[j].second >= 2) {
std::cout << "YES\n";
return;
}
}
i = j;
}
}
std::cout << "NO\n";
}
D. Baggage Claim
题意:一个\(n\times m\)的矩阵,有一个\(2\times k + 1\)的路径,没有一个点经过两次以上。现在给出路径奇数位的位置,求合法的路径数。
首先如果两个点的曼哈顿距离不是\(2\)无解。
否则我们可以建图,发现两个点直接可以选择的点要么是1个要么是2个,如果两个点某个坐标相同,则只有一种选法,否则有两种,但可能有些点被多个点选择。所以我们把只有一种选法的看作两个相同的点,然后对于两个点连边,两个点有边意味着必须在两个点里选一个。同时以给出的奇数点是必须的,它这个点到这个点连一条边。
那么就组成了多个联通块,每个联通块的方案数乘起来就行了。
对于一个联通块,如果边数大于点数,显然没有合适的选法,答案直接是\(0\)。
如果边数等于点数,那么如果这个联通块是一个环,则有两种选法,因为两个点直接最多两个点选择,选择其中一个其它的也就确定了。如果不是一个环,意味着有些点自己连自己的了,那么方案数是\(1\)。
如果边数等于点数减一,则随便选一个点就确定了方案,方案数是点数个数。
点击查看代码
struct DSU {
std::vector<int> fa, cnt;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n, 0);
cnt.assign(_n, 1);
std::iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
cnt[x] += cnt[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
int size(int x) {
return cnt[find(x)];
}
};
const int mod = 1e9 + 7;
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<int> x(k + 1), y(k + 1);
for (int i = 0; i <= k; ++ i) {
std::cin >> x[i] >> y[i];
-- x[i], -- y[i];
}
auto get = [&](int i, int j) -> int {
return i * m + j;
};
DSU dsu(n * m);
std::vector<int> cnt(n * m);
std::vector<int> st(n * m);
for (int i = 0; i <= k; ++ i) {
cnt[get(x[i], y[i])] = 1;
st[get(x[i], y[i])] = 1;
}
for (int i = 0; i + 1 <= k; ++ i) {
if (std::abs(x[i] - x[i + 1]) + std::abs(y[i] - y[i + 1]) != 2) {
std::cout << 0 << "\n";
return;
}
int x1, y1, x2, y2;
if (x[i] == x[i + 1]) {
x1 = x2 = x[i];
y1 = y2 = y[i] + y[i + 1] >> 1;
} else if (y[i] == y[i + 1]) {
y1 = y2 = y[i];
x1 = x2 = x[i] + x[i + 1] >> 1;
} else {
x1 = x[i + 1], y1 = y[i];
x2 = x[i], y2 = y[i + 1];
}
int u = get(x1, y1), v = get(x2, y2);
if (!dsu.same(u, v)) {
cnt[dsu.find(u)] += cnt[dsu.find(v)];
st[dsu.find(u)] |= st[dsu.find(v)];
dsu.merge(u, v);
}
cnt[dsu.find(u)] += 1;
if (u == v) {
st[dsu.find(u)] = 1;
}
}
int ans = 1;
for (int i = 0; i < n * m; ++ i) {
if (dsu.find(i) == i) {
int cnt1 = dsu.size(i);
if (cnt1 < cnt[i]) {
ans = 0;
} else if (cnt1 == cnt[i]) {
if (!st[i]) {
ans = (i64)ans * 2 % mod;
}
} else {
ans = (i64)ans * cnt1 % mod;
}
}
}
std::cout << ans << "\n";
}