比赛链接:
https://codeforces.com/contest/1649
A. Game
题目大意:
\(n\) 个连续的位置,0 表示海洋,1 表示陆地,只可以从一个陆地到另一个陆地上,从一个陆地到相邻的陆地不用花费,但是从第 \(i\) 个陆地到第 \(i + x\) 个陆地需要花费 \(x\),且该移动只能进行一次。
思路:
因为跨陆地移动只能操作一次,所以我们应该从起点先通过临近移动,移动到能到的最远的陆地,然后移动到可以通过临近移动移动到第 \(n\) 个陆地的那个陆地。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define LL long long
LL T = 1, n;
void solve(){
cin >> n;
vector <int> a(n);
int p = 0, q = n - 1;
for (int i = 0; i < n; ++ i)
cin >> a[i];
while (a[p] == a[p + 1] && p < n) p++;
while (a[q] == a[q - 1] && q > 0) q--;
if (p < q) cout << q - p << "\n";
else cout << "0\n";
}
int main(){
IOS();
cin >> T;
while(T--)
solve();
return 0;
}
B. Game of Ball Passing
题目大意:
\(n\) 个人玩传球游戏,现在只知道每个人传了几次球,求出最少需要几个球才能完成这个游戏。
思路:
定义 \(s\) 为 \(n\) 个人传球次数的总和,\(m\) 为 \(n\) 中最多的传球次数。
容易知道当 \(m * 2 <= s\) 时,只需要一个球。有一个特殊情况,当所有人的传球次数都是 0 的时候,答案为 0。
当 \(m * 2 > s\) 时,传球次数就是 \(m * 2 - s\)。因为我们要将这种情况转化为上一种情况,也就是让 \(m * 2 <= s\),所以容易知道传球次数就是 \(m * 2 - s\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define all(x) (x).begin(), (x).end()
#define IOS() ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define LL long long
LL T = 1, n;
void solve(){
cin >> n;
vector <LL> a(n);
for (int i = 0; i < n; ++ i)
cin >> a[i];
LL s = accumulate(all(a), 0LL), m = *max_element(all(a));
if (m == 0) cout << "0\n";
else if (m * 2 <= s) cout << "1\n";
else cout << 2 * m - s << "\n";
}
int main(){
IOS();
cin >> T;
while(T--)
solve();
return 0;
}
C. Weird Sum
题目大意:
\(n * m\) 的网格,每一个格子有一个数字,同一个数字的格子可以组成一对,它们之间的距离以曼哈顿距离计算,求出网格中每一对的距离之和。
思路:
定义第 \(i\) 种数字总共有 \(len\) 个,分别为 (r_0, c_0),(r_1, c_1),...,(r_{len - 1}, c_{len - 1})。
要求的是 \(\sum_{i = 0}^{len - 1}\sum_{j = i + 1}^{len - 1} \lvert r_i - r_j \rvert + \lvert c_i - c_j \rvert\) = \(\sum_{i = 0}^{len - 1}\sum_{j = i + 1}^{len - 1} \lvert r_i - r_j \rvert + \sum_{i = 0}^{len - 1}\sum_{j = i + 1}^{len - 1} \lvert c_i - c_j \rvert\)。
两个的求法其实一样,我们以 \(r\) 为例,如果将 \(r\) 升序排好,那结果就变成 \(\sum_{i = 0}^{len - 1}\sum_{j = i + 1}^{len - 1} (r_j - r_i) = \sum_{i = 0}^{len - 1}\sum_{j = i + 1}^{len - 1} s_j - \sum_{i = 0}^{len - 1}\sum_{j = i + 1}^{len - 1} r_i = \sum_{j = 0}^{len - 1} j * r_j - \sum_{i = 0}^{len - 1}(len - 1 - i) * r_i = \sum_{i = 0}^{2 * i + 1 - len} * r_i\)
按照格子中的数将点分开,然后将它们横纵坐标分别存下来,分别排序,接着按照公式求解。
代码:
#include <bits/stdc++.h>
using namespace std;
#define all(x) (x).begin(), (x).end()
#define IOS() ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define LL long long
#define pb push_back
const int N = 1e5 + 10;
LL n, m, ans, k;
vector <LL> r[N], c[N];
int main(){
IOS();
cin >> n >> m;
for (int i = 0; i < n; ++ i)
for (int j = 0; j < m; ++ j){
LL x;
cin >> x;
r[x].pb(i);
c[x].pb(j);
k = max(x, k);
}
for (int i = 1; i <= k; ++ i){
sort(all(r[i]));
LL l = 0;
for (auto x : r[i]){
ans += (2 * l + 1 - r[i].size()) * x;
l++;
}
}
for (int i = 1; i <= k; ++ i){
sort(all(c[i]));
LL l = 0;
for (auto x : c[i]){
ans += (2 * l + 1 - c[i].size()) * x;
l++;
}
}
cout << ans << "\n";
return 0;
}
D. Integral Array
题目大意:
给 \(n\) 个数的一个序列,选择序列中任意两个数 \(x\)、\(y\),满足 \(x >= y\),\(\lfloor \frac{x}{y} \rfloor\) 需要在序列中出现。判断序列符不符合要求。
思路:
设倍数为 \(k\),那么 \(y * k <= x < y * (k + 1)\)。通过枚举每一个 \(y\) 和 \(k\),去找序列中有没有 \(x\) 存在,判断存不存在可以通过前缀和,即 \(cnt[y * (k + 1) - 1] - cnt[y * k - 1]\),如果该值大于 0,那么就说明序列中有这个值。
代码:
#include <bits/stdc++.h>
using namespace std;
#define all(x) (x).begin(), (x).end()
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
int T, n, c, a;
void solve(){
cin >> n >> c;
vector <int> cnt(c + 1, 0);
for (int i = 0; i < n; ++ i){
cin >> a;
cnt[a]++;
}
for (int i = 1; i <= c; ++ i)
cnt[i] += cnt[i - 1];
for (int i = 1; i <= c; ++ i)
if (cnt[i] - cnt[i - 1])
for (int j = 1; i * j <= c; ++ j)
if (cnt[min(i * (j + 1) - 1, c)] - cnt[i * j - 1] && cnt[j] == cnt[j - 1]){
cout << "No\n";
return;
}
cout << "Yes\n";
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
E. Tyler and Strings
题目大意:
已知长为 \(n\) 的序列 \(s\) 和 长为 \(m\) 的序列 \(t\),将 \(s\) 重新排序,使得到的序列字典序要比 \(t\) 小,问有多少种可能,答案对 998244353 取模。(序列的字典序比较与字符串类似,只是将每一位上的字符比较变为了每一位上的数字比较)
思路:
考虑第 \(i\) 位上的情况,当 \(s_i < t_i\) 且前面每一位上的数字都相同时,后面可以任意排序。
记剩下的数字有 \(tot\) 个,每一种数字有 \(num_i\),总共有 \(k\) 种,小于 \(t[i]\) 的数字总共有 \(cnt\) 个。
不考虑第 \(i\) 位的总的排序方式为 \(\frac{tot!}{num_1!num_2!...num_k!}\),现在因为 \(s_i < t_i\),所以答案为 \(\frac{(tot - 1)!}{num_1!num_2!...num_k!} * cnt\),显然,暴力计算不可以。
可以发现 \(\frac{(tot - 1)!}{num_1!num_2!...num_k!} * cnt = \frac{tot!}{num_1!num_2!...num_k!} * cnt / tot\),前面的就是剩余数字的总的排序方案,可以通过递推来快速计算。
当 \(s_i == t_i\) 时,要考虑下一位的可能,同时将 \(s_i\) 这一种数字的个数减一,因为它在这一位上已经被使用了。
综上,要记录当前剩下数字的总数以及每一种数字的数量,因为每一种数字的数量是动态变化的,所以用线段树或者树状数组维护。
有除又有取模,要预处理一下逆元数组来计算。
还有一个特殊情况要考虑,当 \(s\) 为 \(t\) 的前缀时,答案需要 + 1,因为这种情况下,剩余的数字为 0,不会计入答案。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int mod = 998244353, N = 2e5 + 10;
LL n, m, tree[N], finv[N], fact[N], s[N], t[N], cnt[N], ans, res;
LL lowbit(LL k){
return k & -k;
}
void update(LL x, LL k){
while (x < N){
tree[x] += k;
x += lowbit(x);
}
}
LL query(LL x){
LL t = 0;
while (x != 0){
t += tree[x];
x -= lowbit(x);
}
return t;
}
LL qp(LL a, LL b){
if (b == 0) return 1;
LL ans = 1;
while (b != 0){
if (b & 1) ans = (ans * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ans;
}
LL inv(LL x){
return qp(x, mod - 2);
}
void init(){
LL maxn = max(n, m);
fact[0] = 1;
for (LL i = 1; i <= maxn; ++ i)
fact[i] = fact[i - 1] * i % mod;
finv[maxn] = inv(fact[maxn]);
for (LL i = maxn - 1; i >= 0; -- i)
finv[i] = finv[i + 1] * (i + 1) % mod;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++ i){
cin >> s[i];
cnt[s[i]]++;
update(s[i], 1);
}
for (int i = 1; i <= m; ++ i)
cin >> t[i];
init();
res = fact[n];
for (int i = 1; i < N; ++ i)
res = ( res * finv[cnt[i]] ) % mod;
for (LL i = 1; i <= min(m, n); ++ i){
ans = ( ans + res * query(t[i] - 1) % mod * inv(n - i + 1) ) % mod;
if (cnt[t[i]] == 0) break;
res = res * inv(n - i + 1) % mod * cnt[t[i]] % mod;
cnt[t[i]]--;
update(t[i], -1);
if (n < m && i == n) ans = ( ans + 1 ) % mod;
}
cout << ans << "\n";
return 0;
}