【寒假复健Day1】Codeforces 894题解

Codeforces Round 894 (Div. 3) - 题解

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 ai1ai时, 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 bi1>bi,则可知在对应 a a a数组中,二者之间必须存在一元素 x x x满足 1 ≤ x ≤ b i 1\leq x\leq b_i 1xbi

  • 时间复杂度: 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(n1)种;
  • 考虑两个相同的的 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{C2xn}种种类,消耗 x x x种口味;剩下的 n − C 2 a n-C^a_2 nC2a种种类,使用第二类情况,消耗 n − C 2 a n-C^a_2 nC2a种口味;答案即 a + n − C 2 a a+n-C^a_2 a+nC2a

​ 对于 m a x { C 2 x ≤ n } max\lbrace C^x_2\leq n\rbrace max{C2xn}可以使用二分求得。

C 2 x ≤ n < C 2 x + 1 C^x_2\leq n<C^{x+1}_2 C2xn<C2x+1时, n − C 2 x ≤ C 2 x n-C^x_2\leq C^x_2 nC2xC2x成立 ( x ≥ 2 ) (x\geq 2) (x2),故第二类情况总是满足。

n ≤ 1 0 18 n\leq 10^{18} n1018,二分时,初始右边界 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(x1)nx<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 dcnt 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} dcnti=didxlast,其中 i d x l a s t idx_{last} idxlast为最后一次看电影的天数下标。

​ 我们依次考虑每一天看最后一场电影的情况,得到 a i − d i a_i-di aidi;再从前 i − 1 i-1 i1场电影中,选择最多 m − 1 m-1 m1场以获得尽量大的娱乐值 m a x 1 ≤ j < i { ∑ a j } \mathop{max}\limits_{1\leq j< i}\lbrace \sum a_j\rbrace 1j<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 aidi+1j<imax{aj}更新答案即可。

​ 对于选择 i i i前面最多 m − 1 m-1 m1场电影的最优选择,可以用优先队列(小根堆)维护前最多 m − 1 m-1 m1场电影的最小值,我们采取以下策略:

  • a i ≤ 0 a_i\leq 0 ai0则当前电影无意义,直接跳过考虑;

  • 若尚未选 m − 1 m-1 m1场电影,则选择观看该场电影,并入堆。

  • 若已选满 m − 1 m-1 m1场电影,则用 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} +aiqmin

  • 时间复杂度: 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 n100,喜提***TLE***。由此我们尝试非常经典的***0/1背包DP***,以怪物强度作为“体积”,求方案可行性;对于每一个可行的划分 X X X,其 Y = ∑ s i − X Y=\sum s_i-X Y=siX,故直接对所有的 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,fsiX)

​ 由于 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 si104,sinmax{si}=106,空间上允许;对于单个测试数据,的时间复杂度为 O ( n ∑ s i ) O(n\sum s_i) O(nsi)方案可行,但是因为多测( t ≤ 100 t\leq 100 t100)的存在,难以接受,故需要进一步优化。

​ 由于我们只关心方案的可行性,故可以采用经典的位运算优化——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 ai1<ai

​ 在第三步时,将每个元素赋为 a i = a i + n − i + 1 a_i=a_i+n-i+1 ai=ai+ni+1;此时我们换一个视角,每相邻两项,前一项比后一项多 加 1 加1 1,即相邻两项的差( a i − a i − 1 a_i-a_{i-1} aiai1)减 1 1 1;而当相邻两项相等时,则会合并。

​ 那每次操作可视为“追逐”,相邻两项之差减 1 1 1,直至相同并合并,整体上直到只剩下一个数字为止。显然,本场“游戏”进行的次数为 m a x { a i − a i − 1 } max\lbrace a_i-a_{i-1}\rbrace max{aiai1},因为相邻两项相同时会合并,并且每次差仅减 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{aiai1}

维护

​ 如果我们根据思维来“暴力”解决这题——每次修改数组,对修改后的数组排序, 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';
	}
}

后记

复健第一天,随便拿个题解糊弄下~

  • 27
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值