Codeforces Round 894 (Div. 3) - 题解
A - Gift Carpet
模拟
从左到右依次扫描每列,依次寻找***v,i,k,a***即可。
- 时间复杂度: O ( n m ) O(nm) O(nm)
- 空间复杂度: O ( n m ) O(nm) O(nm)
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
string arr[N];
const string T = "vika";
int main() {
int t;
cin >> t;
while (t--) {
int n, m, i = -1, f = 1;
cin >> n >> m;
for (int y = 0; y < n; y++) cin >> arr[y];
for (int x = 0; x < T.size(); x++, f = 1)
while (f && ++i < m)
for (int j = 0; j < n && f; j++)
if (arr[j][i] == T[x]) f = 0;
cout << (i < m ? "YES\n" : "NO\n");
}
}
B - Sequence Game
模拟
当 a i − 1 ≤ a i a_{i-1}\leq a_i ai−1≤ai时, a i a_i ai会被填入数组 b b b中,我们观察可以得知,一段(最长)连续上升序列时,处第一个元素外,剩余元素均被添加入 b b b中,如 [ 3 , 1 , 2 , 3 , 1 , 2 , 3 ] [3,1,2,3,1,2,3] [3,1,2,3,1,2,3]中,数组 b b b为 [ 3 , 2 , 3 , 2 , 3 ] [3,2,3,2,3] [3,2,3,2,3]。
故我们将 b b b数组遍历一遍,发现出现“断层”,即 b i − 1 > b i b_{i-1}>b_i bi−1>bi,则可知在对应 a a a数组中,二者之间必须存在一元素 x x x满足 1 ≤ x ≤ b i 1\leq x\leq b_i 1≤x≤bi。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
int arr[N], res[N * 2];
int main() {
int t;
cin >> t;
while (t--) {
int n, len = 1;
cin >> n >> arr[0];
res[0] = arr[0];
for (int i = 1; i < n; i++) {
cin >> arr[i];
if (arr[i] < arr[i - 1]) res[len++] = 1;//[1,arr[i]]
res[len++] = arr[i];
}
cout << len << '\n';
for (int i = 0; i < len; i++) cout << res[i] << ' ';
cout << '\n';
}
}
C - Flower City Fence
模拟
根据题意,将木板反置,下标 i i i处对应板高 h h h,反置后的意义为下标 h h h处对应板高 i i i,并且是从开始连续至下标为 h h h处的板高最高可知 i i i,由于板高高度递减,答案也必然是递减的,我们记录下标 i d x idx idx,依次推动下标 i d x idx idx至 i i i,并且将 ( i d x ′ , i ] (idx',i] (idx′,i]的部分赋为 h h h即可。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
int arr[N], res[N];
int main() {
int t;
cin >> t;
while (t--) {
int n, idx = 0, f = 1;
cin >> n;
for (int i = 0; i < n; i++) cin >> arr[i];
for (int i = n - 1; i >= 0; i--)
while (idx < arr[i] && idx < n) res[idx++] = i + 1;
for (int i = 0; i < n && f; i++)
if (arr[i] != res[i]) f = 0;
cout << (f ? "YES\n" : "NO\n");
}
}
D - Ice Cream Balls
二分
- 所有的都是不同的 1 ^1 1:假设有 n n n个不同口味,可以组合出 { x , y } ( x ≠ y ) \lbrace x,y\rbrace (x\neq y) {x,y}(x=y)类型共 C 2 n = n ( n − 1 ) 2 C_2^n=\frac{n(n-1)}{2} C2n=2n(n−1)种;
- 考虑两个相同的的 2 ^2 2:每有两个相同口味,可以组合出 { x , x } \lbrace x,x\rbrace {x,x},答案 + 1 +1 +1。
所以我们先考虑第一类情况,得到 C 2 a = m a x { C 2 x ≤ n } C^a_2=max\lbrace C^x_2\leq n\rbrace C2a=max{C2x≤n}种种类,消耗 x x x种口味;剩下的 n − C 2 a n-C^a_2 n−C2a种种类,使用第二类情况,消耗 n − C 2 a n-C^a_2 n−C2a种口味;答案即 a + n − C 2 a a+n-C^a_2 a+n−C2a。
对于 m a x { C 2 x ≤ n } max\lbrace C^x_2\leq n\rbrace max{C2x≤n}可以使用二分求得。
C 2 x ≤ n < C 2 x + 1 C^x_2\leq n<C^{x+1}_2 C2x≤n<C2x+1时, n − C 2 x ≤ C 2 x n-C^x_2\leq C^x_2 n−C2x≤C2x成立 ( x ≥ 2 ) (x\geq 2) (x≥2),故第二类情况总是满足。
n ≤ 1 0 18 n\leq 10^{18} n≤1018,二分时,初始右边界 r r r直接取值 n n n可能导致溢出,因为 C 2 x = x ( x − 1 ) 2 ≤ n ⇒ x < 2 n C^x_2=\frac{x(x-1)}{2}\leq n\Rightarrow x<2\sqrt{n} C2x=2x(x−1)≤n⇒x<2n,故取 r = m i n ( n , 2 × 1 0 9 ) r=min(n,2\times10^9) r=min(n,2×109)
- 时间复杂度: O ( log n ) O(\log n) O(logn)
- 空间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAX = 2e9;
int main() {
int t;
cin >> t;
while (t--) {
LL n;
cin >> n;
LL l = 2;
LL r = min(MAX, n);
while (l < r) {
LL mid = (l + r + 1) / 2;
LL t = mid * (mid - 1) / 2;
if (t == n) l = r = mid;
else if (t > n) r = mid - 1;
else l = mid;
}
cout << l + (n - l * (l - 1) / 2) << '\n';
}
}
E - Kolya and Movie Theatre
思维, STL(priority_queue)
每次看电影,其娱乐值的衰退值 d ⋅ c n t d\cdot cnt d⋅cnt的 c n t cnt cnt并非间隔天数,而是绝对天数差(相邻两天也算作一天),故衰退值之和,可以视为与最后一场电影的选取有关, ∑ d ⋅ c n t i = d ⋅ i d x l a s t \sum d\cdot cnt_i=d\cdot idx_{last} ∑d⋅cnti=d⋅idxlast,其中 i d x l a s t idx_{last} idxlast为最后一次看电影的天数下标。
我们依次考虑每一天看最后一场电影的情况,得到 a i − d i a_i-di ai−di;再从前 i − 1 i-1 i−1场电影中,选择最多 m − 1 m-1 m−1场以获得尽量大的娱乐值 m a x 1 ≤ j < i { ∑ a j } \mathop{max}\limits_{1\leq j< i}\lbrace \sum a_j\rbrace 1≤j<imax{∑aj},用 a i − d i + m a x 1 ≤ j < i { ∑ a j } a_i-di+\mathop{max}\limits_{1\leq j< i}\lbrace \sum a_j\rbrace ai−di+1≤j<imax{∑aj}更新答案即可。
对于选择 i i i前面最多 m − 1 m-1 m−1场电影的最优选择,可以用优先队列(小根堆)维护前最多 m − 1 m-1 m−1场电影的最小值,我们采取以下策略:
-
若 a i ≤ 0 a_i\leq 0 ai≤0则当前电影无意义,直接跳过考虑;
-
若尚未选 m − 1 m-1 m−1场电影,则选择观看该场电影,并入堆。
-
若已选满 m − 1 m-1 m−1场电影,则用 a i a_i ai与堆顶元素 q m i n q_{min} qmin比较,若 q m i n < a i q_{min}<a_i qmin<ai,则用 a i a_i ai更新 q m i n q_{min} qmin: q m i n q_{min} qmin出堆, a i a_i ai入堆,和 + a i − q m i n +a_i-q_{min} +ai−qmin。
-
时间复杂度: O ( n log n ) O(n\log n) O(nlogn)
- priority_queue(优先队列),由二叉堆实现,插入、查询堆顶元素的时间复杂度均为 log n \log n logn;
-
空间复杂度: O ( ) n O()n O()n
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 1;
int main() {
int t;
cin >> t;
while (t--) {
LL sum = 0, res = 0;
LL n, m, d;
cin >> n >> m >> d;
priority_queue<int, vector<int>, greater<int>> q;
for (int i = 1, x; i <= n; i++) {
cin >> x;
if (x > 0) {
res = max(res, sum + x - d * i);
if (q.size() < m - 1) q.push(x), sum += x;
else if (m > 1 && q.top() < x) {
sum += x - q.top();
q.pop();
q.push(x);
}
}
}
cout << res << '\n';
}
}
F - Magic Will Save the World
DP, bitset
显然,应该将怪物按照使用的魔法类型分为两类——水魔法消灭和冰魔法消灭。使用水魔法消灭的怪兽的强度之和记为 X X X,火魔法则记为 Y Y Y,则至少需要 m a x ( ⌈ X w ⌉ , ⌈ Y f ⌉ ) max(\lceil\frac{X}{w}\rceil,\lceil\frac{Y}{f}\rceil) max(⌈wX⌉,⌈fY⌉)。
讨论划分方法,如果直接搜索枚举所有可能划分方法,枚举可达 2 n 2^n 2n种,由于 n ≤ 100 n\leq100 n≤100,喜提***TLE***。由此我们尝试非常经典的***0/1背包DP***,以怪物强度作为“体积”,求方案可行性;对于每一个可行的划分 X X X,其 Y = ∑ s i − X Y=\sum s_i-X Y=∑si−X,故直接对所有的 s i s_i si进行***DP***,计算 m a x ( ⌈ X w ⌉ , ⌈ ∑ s i − X f ⌉ ) max(\lceil\frac{X}{w}\rceil,\lceil\frac{\sum s_i-X}{f}\rceil) max(⌈wX⌉,⌈f∑si−X⌉)。
由于 s i ≤ 1 0 4 , ∑ s i ≤ n ⋅ m a x { s i } = 1 0 6 s_i\leq10^4,\sum s_i\leq n\cdot max\lbrace s_i\rbrace=10^6 si≤104,∑si≤n⋅max{si}=106,空间上允许;对于单个测试数据,的时间复杂度为 O ( n ∑ s i ) O(n\sum s_i) O(n∑si)方案可行,但是因为多测( t ≤ 100 t\leq 100 t≤100)的存在,难以接受,故需要进一步优化。
由于我们只关心方案的可行性,故可以采用经典的位运算优化——bitset,其运算操作常数极小,我们暂且视为 O ( 1 ) O(1) O(1)。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( ∑ s i ) O(\sum s_i) O(∑si)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 1;
bitset<N> dp;
int main() {
int t;
cin >> t;
while (t--) {
int n, w, f, res = INT_MAX, sum = 0;
cin >> w >> f >> n;
dp.reset();
dp[0] = 1;
for (int i = 0, x; i < n; i++) {
cin >> x;
sum += x;
dp |= (dp << x);
}
for (int i = 0; i <= sum; i++)
if (dp[i]) res = min(res, max((i + w - 1) / w, (sum - i + f - 1) / f));
cout << res << '\n';
}
}
G - The Great Equalizer
STL(multiset)
思维
每次操作都会将数组非递减排序 1 ^1 1,并且删除重复元素 2 ^2 2。经过这两步操作后,数组为严格升序,即 a i − 1 < a i a_{i-1}<a_i ai−1<ai。
在第三步时,将每个元素赋为 a i = a i + n − i + 1 a_i=a_i+n-i+1 ai=ai+n−i+1;此时我们换一个视角,每相邻两项,前一项比后一项多 加 1 加1 加1,即相邻两项的差( a i − a i − 1 a_i-a_{i-1} ai−ai−1)减 1 1 1;而当相邻两项相等时,则会合并。
那每次操作可视为“追逐”,相邻两项之差减
1
1
1,直至相同并合并,整体上直到只剩下一个数字为止。显然,本场“游戏”进行的次数为
m
a
x
{
a
i
−
a
i
−
1
}
max\lbrace a_i-a_{i-1}\rbrace
max{ai−ai−1},因为相邻两项相同时会合并,并且每次差仅减
1
1
1,所以后一项总是大于前一项,“游戏”开始时排序后的最后一项(即最大的数组)会保留到最后,并且每次操作仅加
1
1
1,故“游戏”最终剩下的数字为:
m
a
x
{
a
i
}
+
m
a
x
{
a
i
−
a
i
−
1
}
max\lbrace a_i\rbrace+max\lbrace a_i-a_{i-1}\rbrace
max{ai}+max{ai−ai−1}
维护
如果我们根据思维来“暴力”解决这题——每次修改数组,对修改后的数组排序, f o r for for循环找出最大的相邻差分,与排序后的最后一项相加,得出答案。显然,时间复杂度为 O ( q n log n ) O(qn\log n) O(qnlogn),喜提***TLE***的好成绩。
此时我们应该使用STL***,来维护差值与快速查找最大差值与最大元素。其允许重复元素的按序查询、修改等,故使用**multiset***。
-
时间复杂度: O ( q log n ) O(q\log n) O(qlogn)
- multiset(多集)由***红黑树***实现,查询、删除、插入操作的时间复杂度均为 O ( log n ) O(\log n) O(logn), n n n为集合大小;
-
空间复杂度: O ( n ) O(n) O(n)
解题代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
int arr[N];
int main() {
cin.tie(0)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n, q, idx, val;
cin >> n;
for (int i = 0; i < n; i++) cin >> arr[i];
if (n == 1) {
cin >> q;
while (q--) {
cin >> idx >> val;
cout << val << ' ';
}
cout << '\n';
continue;
}
multiset<int> a, s;//数组; 差值
for (int i = 0; i < n; i++) a.insert(arr[i]);
for (auto p = a.begin(), pr = a.begin(); ++p != a.end(); pr = p) s.insert(*p - *pr);
cin >> q;
while (q--) {
cin >> idx >> val;
//删除原idx下标的元素
auto p = a.find(arr[idx - 1]);
auto prve = p, next = p;
prve--, next++;
if (p == a.begin()) s.erase(s.find (*next - *p));//头
else if (p == --a.end()) s.erase(s.find (*p - *prve));//尾
else {//中间
s.erase(s.find(*next - *p));//使用.find()防止删除多个相同值的元素
s.erase(s.find(*p - *prve));
s.insert(*next - *prve);
}
a.erase(p);
//添加val
prve = next = p = a.insert(val);
prve--, next++;
if (p == a.begin()) s.insert(*next - *p);//头
else if (p == --a.end()) s.insert(*p - *prve);//尾
else {//中间
s.insert(*next - *p);
s.insert(*p - *prve);
s.erase(s.find (*next - *prve));
}
arr[idx - 1] = val;
cout << *--a.end() + *--s.end() << ' ';
}
cout << '\n';
}
}
后记
复健第一天,随便拿个题解糊弄下~