比赛链接:https://codeforces.com/contest/1995
A. Diagonals
题意
给一个 n ∗ n n*n n∗n 的棋盘,要放上 k k k 个棋子,问最少被放棋子的正对角线有多少条?
数据范围
1 ≤ n ≤ 100 , 0 ≤ k ≤ n 2 1 \le n \le 100, 0 \le k \le n^2 1≤n≤100,0≤k≤n2
思路
正对角线有 2 n − 1 2n-1 2n−1 条,能放的棋子数从大到小依次为 n , n − 1 , n − 1 , n − 2 , n − 2 , . . . 1 , 1 n,n-1,n-1,n-2,n-2,...1,1 n,n−1,n−1,n−2,n−2,...1,1,即棋子数属于 [ 1 , n − 1 ] [1,n-1] [1,n−1] 的对角线有两条,棋子数为 n n n 的对角线有一条。
要使得被放棋子的对角线尽可能少,可以把棋子从最长的对角线开始放,直到棋子放完为止。
复杂度
时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)
代码实现
// Problem: A. Diagonals
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/A
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n, k;
cin >> n >> k;
// 对角线按从大到小排
vector<int> arr = { n };
for (int i = n - 1; i >= 1; i--) {
arr.push_back(i);
arr.push_back(i);
}
// 特判 k = 0 的情况
if (k == 0) {
cout << 0 << '\n';
return;
}
int ans = 0;
for (int i : arr) {
if (k > i) {
ans++;
k -= i;
} else {
ans++;
break;
}
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
B. Bouquet
题意
有 n n n 种花,每种花的花瓣数不同,购买花需要的金币与购买的花的花瓣总数相同,购买的花的两两之间的花瓣数不能超过 1 1 1。
问有 m m m 枚金币能买的花瓣总数是多少?
数据范围
1
≤
n
≤
2
⋅
1
0
5
,
1
≤
m
≤
1
0
18
1 \le n \le 2 \cdot 10^5, 1 \le m \le 10^{18}
1≤n≤2⋅105,1≤m≤1018
1
≤
a
i
≤
1
0
9
1 \le a_i \le 10^9
1≤ai≤109 (每种花的花瓣数)
1
≤
c
i
≤
1
0
9
1 \le c_i \le 10^9
1≤ci≤109 (每种花的数量)
思路
如果当前购买的花的花瓣数为 x x x,该种花的数量为 y y y,那么该种花能最多购买的数量 k = m i n ( y , ⌊ m x ⌋ ) k =min(y,\lfloor \frac{m}{x} \rfloor) k=min(y,⌊xm⌋)。
如果花瓣数为 x + 1 x+1 x+1,数量为 y 1 y_1 y1,那么可以再购买花瓣数为 x + 1 x+1 x+1 的花的数量 k 1 = m i n ( y 1 , ⌊ m − k ∗ x x + 1 ⌋ ) k_1 = min(y_1,\lfloor \frac{m-k*x}{x+1} \rfloor) k1=min(y1,⌊x+1m−k∗x⌋)。
经过上面的购买后,得到的花瓣总数为 k ∗ x + k 1 ∗ ( x + 1 ) = ( k + k 1 ) ∗ x + k 1 k*x + k_1*(x+1)=(k+k_1)*x+k_1 k∗x+k1∗(x+1)=(k+k1)∗x+k1,剩下的金币为 m − ( k + k 1 ) ∗ x − k 1 m-(k+k_1)*x-k_1 m−(k+k1)∗x−k1,要使得花瓣总数再增加,考虑把花瓣数为 x x x 的花替换成花瓣数为 x + 1 x+1 x+1 的花。
因为替换的花数量不能超过购买的花瓣数为 x x x 花的数量 k k k 和还没被购买的 x + 1 x+1 x+1 的花的数量 y 1 − k 1 y_1-k_1 y1−k1,且多出来的花费也不能超过剩下的金币数 m − ( k + k 1 ) ∗ x − k 1 m-(k+k_1)*x-k_1 m−(k+k1)∗x−k1,所以最多能对 m i n ( k , y 1 − k 1 , m − ( k + k 1 ) ∗ x − k 1 ) min(k,y_1-k_1,m-(k+k_1)*x-k_1) min(k,y1−k1,m−(k+k1)∗x−k1) 朵花进行替换。
综上所述,购买花瓣数为 x x x 的花的最多花瓣总数为 k + k 1 + m i n ( k , y 1 − k 1 , m − ( k + k 1 ) ∗ x − k 1 ) k+k_1 + min(k,y_1-k_1,m-(k+k_1)*x-k_1) k+k1+min(k,y1−k1,m−(k+k1)∗x−k1),对每种花计算最多花瓣总数取最大值即为答案。
因为给出每种花的花瓣数不保证是有序的,所以为方便计算花瓣总数,可以读入后按花瓣数进行排序,或者直接用 m a p / u n o r d e r e d _ m a p map/unordered\_map map/unordered_map 存下每种特定花瓣数的花的数量。
复杂度
空间复杂度均为 O ( n ) O(n) O(n),时间复杂度为 O ( n ) O(n) O(n) (用哈希表存)或者 O ( n log n ) O(n \log n) O(nlogn) (排序或者用 m a p map map 存)
代码实现
// Problem: B2. Bouquet (Hard Version)
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/B2
// Memory Limit: 256 MB
// Time Limit: 1500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
int n, m;
int a[N], c[N];
map<int, int> cnt;
void solve()
{
cin >> n >> m;
cnt.clear();
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
cnt[a[i]] = x;
}
int ans = 0;
for (auto it : cnt) {
int x = it.first, y = it.second;
int k = min(m / x, y);
int cur = x * k;
// 当前能取得的最大花瓣总数
int cost = m - x * k;
if (cnt.count(x + 1)) {
int y1 = cnt[x + 1];
int k1 = min(cost / (x + 1), y1);
cur += k1 * (x + 1);
cost -= k1 * (x + 1);
// 购买后的剩下金币
cur += min({ cost, y1 - k1, k });
}
ans = max(ans, cur);
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
C. Squaring
题意
给一个长度为 n n n 的数组 a a a,每次操作可以把数组中的一个数变成这个数平方之后的结果,问将数组 a a a 变成不递减的最小操作次数。
数据范围
1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10 ^5 1≤n≤2⋅105, 1 ≤ a i ≤ 1 0 6 1 \le a_i \le 10 ^ 6 1≤ai≤106
思路
注意到 1 1 1 比较特殊,因为 1 1 1 平方后仍然为 1 1 1,所以如果存在 a i − 1 > a i a_{i-1}>a_i ai−1>ai, a i = 1 a_i=1 ai=1,此时一定无解,因为无法对 a i a_i ai 进行操作使得 a i ≥ a i − 1 a_i \ge a_{i-1} ai≥ai−1,除此之外均有解。
如果前 i − 1 i-1 i−1 个数已通过最少操作次数变成不递减,此时把 a i a_{i} ai 操作到恰好不小于 a i − 1 a_{i-1} ai−1,增加的的操作次数最少,这就得到把前 i i i 个数变成不递减的最小操作次数。
发现这存在着从左到右的递推关系,因为第一个数不需要操作,所以从 2 2 2 到 n n n 进行操作,统计到每个数的最少操作次数总和即为答案。
一个数 x x x 经过 y y y 次平方操作,会变成 x 2 y x^{2^y} x2y,因为每次操作都是在指数上多乘上一个 2 2 2。
如果 a i − 1 a_{i-1} ai−1 经过 b i − 1 b_{i-1} bi−1 次平方操作,那么 a i − 1 a_{i-1} ai−1 会变成 a i − 1 2 b i − 1 a_{i-1}^{2^{b_{i-1}}} ai−12bi−1,可以通过二分查找找到最小的 a i 2 x a_{i}^{2^x} ai2x 满足 a i 2 x ≥ a i − 1 2 b i − 1 a_{i}^{2^x} \ge a_{i-1}^{2^{b_{i-1}}} ai2x≥ai−12bi−1, x x x 即为 a i a_{i} ai 需要进行的最小操作次数。
注意到在上面的式子中,指数可能会很大导致超出数据范围,因此需要考虑怎么变式使得可以在数据范围内进行大小比较。
涉及指数可以把指数形式化为对数形式,对两边取对数可以得到 2 x ∗ log a i ≥ 2 b i − 1 ∗ log a i − 1 2^x *\log a_i \ge 2^{b_{i-1}} *\log a_{i-1} 2x∗logai≥2bi−1∗logai−1。
因为 2 x 2^x 2x 也可能很大,所以对上面的式子再次两边取对数,得到 x ∗ log 2 + log ( log a i ) ≥ b i − 1 ∗ log 2 + log ( log a i − 1 ) x*\log 2+\log(\log a_i) \ge b_{i-1}*\log 2 + \log(\log a_{i-1}) x∗log2+log(logai)≥bi−1∗log2+log(logai−1),这样就可以进行比较了。
细节
注意浮点数运算会有误差,判断两个浮点数是否相等,需要比较它们的差值是否在规定的最小差值内,如果不超过则视为相等,该最小差值不能大于 1 0 − 8 10^{-8} 10−8。
复杂度
空间复杂度 O ( n ) O(n) O(n),时间复杂度 O ( n log n ) O(n\log n) O(nlogn)
代码实现
// Problem: C. Squaring
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n;
int a[N], b[N];
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = 0;
}
for (int i = 2; i <= n; i++) {
// 无解的情况
if (a[i] == 1 && a[i - 1] != 1) {
cout << -1 << '\n';
return;
}
}
for (int i = 2; i <= n; i++) {
int l = 0, r = 2e5;
long double lx = b[i - 1] * log(2) + log(log(a[i - 1]));
// lx 为 a[i-1] 进行最小操作后的结果(对数化后的)
while (l < r) {
int mid = (l + r) >> 1;
long double rx = mid * log(2) + log(log(a[i]));
if (lx <= rx || fabs(lx - rx) <= 1e-8)
r = mid;
else
l = mid + 1;
}
b[i] = r;
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans += b[i];
// b[i] 为 a[i] 的最小操作次数
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
D. Cases
题意
给一个由 c c c 种字符组成的,长度为 n n n 的字符串,要将字符串分割成长度不超过 k k k 的若干子串,问子串的结尾字符最少有多少种。
数据范围
1 ≤ k ≤ n ≤ 2 18 1 \le k \le n \le 2^{18} 1≤k≤n≤218, 1 ≤ c ≤ 18 1 \le c \le 18 1≤c≤18
思路
要将字符串分割成长度不超过 k k k 的若干子串,意味着对于任意一种合法结尾字符集合 S S S,每个长度为 k k k 的子区间至少有该集合的一个结尾字符。
因为如果存在着长度为 k k k 的子区间不含 S S S 中结尾字符,那么该区间会包含在长度大于 k k k 的子串,集合 S S S 就不合法了。
令 f T f_T fT 为不包含一些结尾字符的字符集合 T T T 是否合法, f T = 1 f_T = 1 fT=1 表示合法, f T = 0 f_T = 0 fT=0 反之。
1,因为对于每个长度为 k k k 的子区间至少存在一个结尾字符在区间内,所以任意一个长度为 k k k 的子区间的包含的字符集合为 S S S,不包含 S S S 的所有字符显然是不合法的,故 f S = 0 f_S = 0 fS=0。
统计每个长度为 k k k 的子区间包含的字符,相当于维护一个长度为 k k k 的窗口,用双指针维护,具体做法是开 c c c 个桶统计每种字符的数量 c n t i cnt_i cnti,每次进入下一个子区间,就把新进入的字符数量加一,统计完一个子区间,就把开头的字符数量减一,这样就能确定每个子区间有哪些字符。
2,同时,最后一段子串必然以字符串的最后一个字符结尾,所以如果字符集合 T T T 不包含字符串的最后一个字符也是不合法的。
3,除了以上两种情况,如果 T T T 存在任意一个子集不合法,说明 T T T 包含了更多了结尾字符也存在不合法的情况,那么 T T T 必然也是不合法的,否则 T T T 是合法的,因此 f T = A N D ( f t ) ( t ∈ T ) f_T = AND(f_t)(t \in T) fT=AND(ft)(t∈T)。
在大小与 T T T 差一的子集时,因为集合 T T T 子集的子集仍然是 T T T 的子集,所以把更小的子集的 A N D AND AND 和也计算进去了,所以 f T f_T fT 可以由大小差一的子集计算而来,即 f T = A N D ( f t ) ( t ∈ T , ∣ T ∣ − ∣ t ∣ = 1 ) f_T = AND(f_t)(t \in T,|T|-|t|=1) fT=AND(ft)(t∈T,∣T∣−∣t∣=1)。
用二进制表示集合 T T T,从高到低第 i i i 位为 1 1 1 表示不含第 i i i 小字符,则 f T = A N D ( f t ) ( t = T − 2 i , 2 i ∈ T ) f_T = AND(f_t)(t = T-2^i,2^i \in T) fT=AND(ft)(t=T−2i,2i∈T)。
从小到大计算 [ 0 , 2 c − 1 ] [0,2^c-1] [0,2c−1] 的合法性,如果集合合法,则对应二进制串中 0 0 0 的数量表示该集合包含的字符,因此子串结尾字符的最少种类为所有合法集合中 0 0 0 的最少数量。
复杂度
空间复杂度 O ( 2 c − 1 ) O(2^c-1) O(2c−1),时间复杂度 O ( n ∗ c + 2 c ∗ c ) O(n*c+2^c*c) O(n∗c+2c∗c)
代码实现
// Problem: D. Cases
// Contest: Codeforces - Codeforces Round 961 (Div. 2)
// URL: https://codeforces.com/contest/1995/problem/D?locale=en
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1 << 18;
int n, m, k, f[N];
string s;
void solve()
{
cin >> n >> m >> k >> s;
s = ' ' + s;
for (int i = 0; i < (1 << m); i++) {
f[i] = 1;
}
int cnt[26] = { 0 };
for (int i = 1; i <= n; i++) {
cnt[s[i] - 'A']++;
// 增加新加入的字符数量
if (i >= k) {
int sta = 0;
for (int j = 0; j < m; j++) {
if (cnt[j])
sta |= (1 << j);
// 存在对应字符
}
f[sta] = 0;
cnt[s[i - k + 1] - 'A']--;
// 删去子区间开头的字符
}
}
int ans = m;
for (int i = 0; i < (1 << m); i++) {
for (int j = 0; j < m; j++) {
if (((i >> j) & 1)) {
f[i] &= f[i ^ (1 << j)];
if (j == s[n] - 'A')
f[i] = 0;
// 如果集合i不包含结尾字符 s[n] 也是不合法的
}
}
if (f[i]) {
int cur = 0;
for (int j = 0; j < m; j++) {
cur += ((i >> j) & 1) == 0;
}
ans = min(ans, cur);
}
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}