【寒假复健Day3】Educational Codeforces Round 161(div. 2)

Educational Codeforces Round 161(div. 2) - 题解

Educational Codeforces Round 161(div. 2)

文章目录

  • Educational Codeforces Round 161(div. 2) - 题解
    • @[toc]
    • A - Tricky Template
    • B - Forming Triangles
    • C - Closest Cities
    • D - Berserk Monsters
    • E - Increasing Subsequences
    • F - Replace on Segment

A - Tricky Template

思维

​ 若在第 i i i个字符处, c i c_i ci a i , b i a_i,b_i ai,bi中至少一个相等,则无法在 t i t_i ti 处构造字符使得 c c c不匹配。反证,若在此处 c i c_i ci无法匹配,则在此处与之相同的 a i a_i ai b i b_i bi也无法匹配,即 a a a b b b无法匹配,不满足题意。

​ 若在第 i i i个字符处, c i c_i ci a i , b i a_i,b_i ai,bi均不相同相等,则在 t i t_i ti处构造 c i c_i ci的大写形式即可,使得 c i c_i ci不满足但 a i , b i a_i,b_i ai,bi均满足。

​ 综上,即是否存在下标 i i i,使 c i ≠ a i c_i\ne a_i ci=ai c i ≠ b i c_i\neq b_i ci=bi

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
#include

using namespace std;

int main() {
	cin.tie(0)->sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--) {
		int n;
		string a, b, c;
		cin >> n >> a >> b >> c;
		int i = -1;
		while (++i < n)
			if (b[i] != c[i] && a[i] != c[i]) break;
		cout << (i == n ? "No\n" : "Yes\n");
	}
}

B - Forming Triangles

数学

​ 每根棍子长度为 2 2 2的整次幂,则最长两根棍子的长度必须相同。若不同,则最长的比剩下的长至少两倍以上,剩下两根之和不大于最长那根,不满足题意。故最长两根长度相同,在此基础上,第三根长度则无限制(当然不能长度最长长度)。

每根棍子的长度为 2 2 2的整次幂 2 k 2^k 2k,不妨设棍子长度为 2 a , 2 b , 2 c ( 2 a ≥ 2 b ≥ 2 c ) 2^a,2^b,2^c(2^a\geq2^b\geq2^c) 2a,2b,2c(2a2b2c),若 2 a > 2 b 2^a>2^b 2a>2b 2 a − 1 ≥ 2 b 2^{a-1}\geq2^b 2a12b,则 2 a = 2 × 2 a − 1 ≥ 2 b + 2 c 2^a=2\times2^{a-1}\geq2^b+2^c 2a=2×2a12b+2c,无法构成三角形,故 2 a = 2 b ≥ 2 c 2^a=2^b\geq2^c 2a=2b2c

​ 若长度为 2 k 2^k 2k的有 x x x根,长度 ≤ 2 k \leq2^k 2k的有 y y y根, x ≥ 2 x\geq2 x2时,仅由两根 2 k 2^k 2k长度的棍子(剩下那根长度 < 2 k <2^k <2k)构成的三角形的个数为 ( x − y ) C x 2 (x-y)C_x^2 (xy)Cx2 x ≥ 3 x\geq3 x3时,由三根 2 k 2^k 2k长度的棍子构成的三角形个数为 C x 3 C_x^3 Cx3。对所有满足条件的 k k k依上述两式求和即可。

  • 时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)
    • 使用map计数时
  • 空间复杂度: O ( n ) O(n) O(n)
#include

using namespace std;
typedef long long LL;

int main() {
	cin.tie(0)->sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--) {
		int n;
		cin >> n;
		map<int, int> cnt;
		for (int i = 0, x; i < n; i++) cin >> x, cnt[x]++;
		LL res = 0, sum = 0;;
		for (const auto& x : cnt) {
			LL v = x.second;
			res += (sum * v * (v - 1) / 2) + (v * (v - 1) * (v - 2) / 6);
			sum += v;
		}
		cout << res << '\n';
	}
}

C - Closest Cities

前缀和

​ 只能向前或向后前行,按向前与向后时的移动花费分别打遍前缀和。

  • 时间复杂度: O ( n + m ) O(n+m) O(n+m)
  • 空间复杂度: O ( n ) O(n) O(n)
#include

using namespace std;
typedef long long LL;
const int N = 1e5 + 1;
LL arr[N] = { INT_MIN }, pre[N], post[N];

int main() {
	cin.tie(0)->sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--) {
		int n, m;
		cin >> n;
		post[n + 1] = 0;
		arr[n + 1] = INT_MAX;
		for (int i = 1; i <= n; i++) cin >> arr[i];
		for (int i = 1; i < n; i++)
			pre[i + 1] = pre[i] + (arr[i] - arr[i - 1] > arr[i + 1] - arr[i] ? 1 : arr[i + 1] - arr[i]);
		for (int i = n; i > 1; i--)
			post[i - 1] = post[i] + (arr[i] - arr[i - 1] > arr[i + 1] - arr[i] ? arr[i] - arr[i - 1] : 1);
		cin >> m;
		while (m--) {
			int l, r;
			cin >> l >> r;
			if (l < r) cout << pre[r] - pre[l] << '\n';
			else cout << post[r] - post[l] << '\n';
		}
	}
}

D - Berserk Monsters

搜索

​ 每个怪物若死亡,则只会影响周围两边的怪物,而非影响全图;故每轮,我们只需要考虑上一轮死亡怪物左右两侧的情况,每个怪物死亡,则将左右两侧的怪物加入更新队列

​ 因为每一轮怪物的攻击是同时进行的,也即如果这个怪物在本轮死亡,也会将伤害传递给左右两侧的怪物。此处步骤要分层,先检查更新队列,将死亡怪物推入移除队列,待更新队列全部检查完毕后,再移除移除队列中的怪物,同时将死亡(被移除)怪物两侧的怪物推入更新队列,如此往复。

​ 使用两个数组模拟双链表,表示该怪物左侧下标与右侧下标。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
#include

using namespace std;
const int N = 3e5 + 2;
int a[N], d[N], vis[N] = { 2 };
int l[N], r[N];
int q1[N], q2[N];

int main() {
	cin.tie(0)->sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--) {
		int n;
		cin >> n;
		a[n + 1] = d[n + 1] = 0;
		vis[n + 1] = 2;
		for (int i = 1; i <= n; i++) cin >> d[i];
		for (int i = 1; i <= n; i++) cin >> a[i], vis[i] = 0, l[i] = i - 1, r[i] = i + 1;
		int idx1 = 0, idx2 = 0;
        
		for (int i = 1; i <= n; i++)
			if (d[i - 1] + d[i + 1] > a[i]) q1[idx1++] = i;//将要死亡的推入更新队列(其实把所有元素都推入亦可)
		for (int k = 1; k <= n; k++) {
			int res = 0;
			for (int i = 0; i < idx1; i++) {//检查更新队列
				int v = q1[i];
				if (vis[v] == 2) continue;//已死亡,避免重复检查带来的额外开销
				if (d[l[v]] + d[r[v]] > a[v]) q2[idx2++] = v, res++, vis[v] = 2;//推入移除队列,标记为2,答案+1,进入移除队列
				else vis[v] = 0;//清空标记,以待本次更新
			}
			idx1 = 0;
			for (int i = 0; i < idx2; i++) {//检查移除队列
				int v = q2[i];
				if (!vis[r[l[v]] = r[v]]) vis[q1[idx1++] = r[v]] = 1;//若右节点为进入更新队列,则加入,并标记为1,表示已进入更新队列
				if (!vis[l[r[v]] = l[v]]) vis[q1[idx1++] = l[v]] = 1;//避免重复检查带来的额外开销(最多可能进入两次)
			}
			idx2 = 0;
			cout << res << (k == n ? '\n' : ' ');
 		} 
	}
}

E - Increasing Subsequences

位运算,构造

​ 假设这样一条主干序列,其严格递增,如 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n,其每个数可以任意选择(甚至全不选),其剩下的均构成严格递增序列,其严格递增子序列个数为 2 n 2^n 2n

​ 在某处插入全序列最小值,如 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5,插入 0 0 0后变为 1 , 2 , 3 , 0 , 4 , 5 1,2,3,0,4,5 1,2,3,0,4,5。该 0 0 0不选,则剩下数构成一条长度为 5 5 5的主干序列,贡献 2 5 2^5 25个严格递增子序列;选择该 0 0 0,则该 0 0 0左侧的所有数均不能选择( 0 0 0为全序列最小数)右侧的 4 , 5 4,5 4,5构成主干序列,可以任意选择,贡献 2 5 2^5 25

​ 如此,我们往一条主干序列中不断插入同一个最小值(若不同则会相互影响构成新的子序列),得到 2 n + 2 a 1 + 2 a 2 + ⋯ = X 2^n+2^{a_1}+2^{a_2}+\cdots=X 2n+2a1+2a2+=X

X = 13 = 110 1 B X=13=1101_B X=13=1101B,主干序列 1 , 2 , 3 1,2,3 1,2,3贡献 2 8 2^8 28,在 1 , 2 1,2 1,2中间插入 0 0 0新增贡献 2 2 = 2 2^2=2 22=2,在 3 3 3后面插入 0 0 0新贡献 2 = 1 2^=1 2=1,得到序列 1 , 0 , 2 , 3 , 0 1,0,2,3,0 1,0,2,3,0,满足题意。

  • 时间复杂度: O ( log ⁡ X ) O(\log{X}) O(logX)
  • 空间复杂度: O ( log ⁡ X ) O(\log{X}) O(logX)
#include

using namespace std;
typedef long long LL;

int main() {
	cin.tie(0)->sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--) {
		LL n, k = 200;
		cin >> n;
		vector<int> res;
		while (n > 1) {
			if (n & 1) res.push_back(0);
			res.push_back(k--);
			n >>= 1;
		}
		cout << res.size() << '\n';
		for (int i = res.size() - 1; i >= 0; i--) cout << res[i] << (i ? ' ' : '\n');
	}
}

F - Replace on Segment

有实力再看,私密马萨

你可能感兴趣的:(Codeforces,算法,数据结构)