Codeforces Round #762 (Div. 3) 题解

Codeforces Round #762 (Div. 3)

A. Square String?(⭐)

题目链接:https://codeforces.com/contest/1619/problem/A

题目大意:

判断给定的字符串 \(s\) 是或否可通过 某一字符串重复一次 得到

\(T\) 组数据,\(1\leq T\leq 100\)

字符串长度为 \(len\)\(1\leq len\leq 100\)

题解:暴力

  • 输出 "NO":\(len\) 为奇数或 \(\exist\ i\) 满足 \(s_i\neq s_{i+\frac{len}2}(1\leq i\leq \frac{len}2)\)
  • 输出 "YES":\(len\) 为偶数且 \(\forall \ i\) 满足 \(s_i=s_{i+\frac{len}2}(1\leq i\leq \frac{len}2)\)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
void solve() {
	char c[105];
	scanf("%s", c + 1);
	int len = strlen(c + 1);
	if(len & 1) return puts("NO"), (void)0;
	for(int i = 1; i * 2 <= len; ++ i) if(c[i] != c[i + len / 2]) return puts("NO"), (void)0;
	puts("YES");
}
int main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

B. Squares and Cubes(⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/B

题目大意:

给定 \(n\),询问 区间 \([1, n]\) 中为 平方数或立方数 的个数

\(T\) 组数据,\(1\leq T\leq 20\)

\(1\leq n\leq 10^9\)

题解:

方法①:容斥原理

由 容斥原理 可得,$ans = $ (平方数)+(立方数)-(平方数&立方数)

那么如何求解(平方数&立方数)呢?

\(x^6=(x^3)^2=(x^2)^3\)

求符合条件的 \(x\) 的数的个数即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
void solve() {
	int x = read(), a, b, c;
	for(a = 1; a * a <= x; ++ a); a--; 
	for(b = 1; b * b * b <= x; ++ b); b --;
	for(c = 1; c * c * c * c * c * c <= x; ++ c); c --;
	printf("%lld\n", a + b - c);
}
signed main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

方法②:\(set\)

分别考虑符合条件的 平方数和立方数,将数据放入 \(set\),自动去重

输出 \(set\) 的大小即可

#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
void solve() {
	int x = read();
	set <int> s;
	for(int i = 1; i * i <= x; ++ i) s.insert(i * i);
	for(int i = 1; i * i * i <= x; ++ i) s.insert(i * i * i);
	printf("%lld\n", s.size());
}
signed main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

C. Wrong Addition(⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/C

题目大意:

考虑新式加法 \(a+b=s\),不进位,且保留每位所计算得到的值

例如 \(17236+3465=1106911\)

给定 \(a\)\(s\),求是否存在符合条件的 \(b\),无解输出 \(-1\)

\(T\) 组数据

\(1\leq T\leq 10^4,1\leq a<s\leq 10^{18}\)

题解:模拟

考虑由 \(s-a\) 得到 \(b\)

从低位向高位依次计算 \(b\) 对应的值

假设当前为 \(s\)\(i\) 位,\(a\)\(j\) 位(从低到高)

如果 \(a[j]+b[j]=10+s[i]\),则有 \(s[i]<a[j]\),基于此进行分类讨论

\(s[i]\geq a[j]\),则 \(b[j]=s[i]-a[j]\)

否则,\(b[j]=s[i+1]*10+s[i]-a[j]\) (注:若 \(s[i+1]\neq 1\),则无解)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
void solve() {
	int a = read(), s = read(), la = 0, ls = 0, tmp, sa[25] = {0}, sb[25] = {0}, ss[25] = {0};
	tmp = a; while(tmp) sa[++ la] = tmp % 10, tmp /= 10;
	tmp = s; while(tmp) ss[++ ls] = tmp % 10, tmp /= 10;
	for(int i = 1, j = 1; i <= ls || j <= la; ++ i, ++ j) {
		if(ss[i] >= sa[j]) {sb[j] = ss[i] - sa[j]; continue;}
		else {
			if(ss[i + 1] != 1) return puts("-1"), (void)0;
			sb[j] = 10 + ss[i ++] - sa[j];
		}
	}
	int ans = 0;
	for(int i = ls; i >= 1; -- i) ans = ans * 10 + sb[i];
	printf("%lld\n", ans);
}
signed main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

D. New Year's Problem(⭐⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/D

题目大意:

给定 \(m*n\) 的矩阵 \(A\),任意选择 \(n-1\) 行,形成 \((n-1)*n\) 的新矩阵 \(B\)

该矩阵所对应的价值 \(val=min(max(B_{i_1,1}),max(B_{i_2,2}),\cdots,max(B_{i_n,n}))\)

要求 最大化 \(val\) 的值

\(T\) 组数据

\(1\le T\leq 10^4,n\geq 2,2\leq n*m\leq10^5,1\leq A_{i,j}\leq10^9\)

题解:

方法①:二分答案

考虑二分答案的判断为 是否存在某种情况使得 \(n-1\) 行的每列都存在 \(\geq x\) 的元素

\(n-1\) 行的限制可以改为 存在某行至少有两个 \(\geq x\) 的元素

该判断具有单调性,二分答案即可

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
int n, m;
vector <int> p[N];
bool check(int x) {
	int tag = 0, vis[n + 5] = {0};
	for(int i = 1; i <= m; ++ i) {
		int c = 0;
		for(int j = 0; j < n; ++ j) {
			if(p[i][j] >= x) vis[j] = 1, c ++;
		}
		if(c > 1) tag = 1;
	}
	if(tag == 0) return 0;
	for(int i = 0; i < n; ++ i) if(! vis[i]) return 0;
	return 1;
}
void solve() {
	int l = 1e9, r = 0;
	m = read(); n = read();
	for(int i = 1; i <= m; ++ i) {
		p[i].clear();
		for(int j = 0, x; j < n; ++ j) {
			p[i].push_back(x = read());
			l = min(l, x);
			r = max(r, x);
		}
	}
	while(l < r) {
		int mid = (l + r + 1) >> 1;
		if(check(mid)) l = mid;
		else r = mid - 1;
	}
	printf("%d\n", l);
}
int main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}
// 1111110000

方法②:

\(mx_j=max(A_{i,j})\)

若不限制行数为 \(n-1\),则 \(ans=min(mx_j)\)

加入限制条件 行数为 \(n-1\) 后,则至少存在某一行被选择两个数

此限制条件仅会对 \(n\) 行都有数被选择的情况 有影响

考虑限制 \(ans\) 的最大值为 每行元素次大值的最大值

该行次大值及最大值 和 其他未选择数据的列的元素最大值 构成了最后所选择的情况

各元素 取最小值即为答案

考虑该做法的正确性:
此做法仅会对 \(n\) 行都有数据被选 的情况产生影响

考虑调整该情况的选择,即为更改某一列或某两列的选择使某行被选择两个数

由对 \(ans\) 的限制可知,其他的选择情况一定不优

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
void solve() {
	int m = read(), n = read(), ans = 0, a[N], mx[N] = {0};
	for(int i = 1; i <= m; ++ i) {
		for(int j = 1; j <= n; ++ j) mx[j] = max(mx[j], a[j] = read());
		sort(a + 1, a + n + 1);
		ans = max(ans, a[n - 1]);
	}
	for(int i = 1; i <= n; ++ i) ans = min(ans, mx[i]);
	printf("%d\n", ans);
}
int main() {
	int T = read();
	while(T --> 0) solve();
	return 0;
}

E. MEX and Increments(⭐⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/E

题目大意:

定义一组数的 \(MEX\) 为该组数中未出现过的最小自然数

给定 \(n\) 个数 \(a_i\),每次操作可花费 \(1\) 的代价使 \(a_i++\),操作次数无限

依次输出使得数组 \(a_i\)\(MEX\) 变成 \(i(i\in[0,n])\) 的最小代价(无解输出 \(-1\)

各输出的代价间相互独立

\(T\) 组数据

\(1\leq T\leq 10^4,1\leq n\leq 2\cdot10^5,0\leq a_i\leq n\)

题解:栈

\(cnt[i]\) 记录 \(i\) 的出现次数

依次考虑 \(MEX\)\(i\) 的情况

用栈来维护 当前可使用的数 的集合

假设当前考虑到 \(i\),需要用数来填补 \(0->(i-1)\)

\(i'=i-1\) 时,已经考虑过填补 \(0->(i-2)\) 的情况

因此只需要考虑能否通过 数据来填补 \(i-1\) 即可

由于栈中维护的是可用数的集合

若栈为空,则无解(\(i'>i\) 时,\(i'\) 的情况也无解)

若栈非空,该次代价为 \((i-1)\ -\ stack[top]\)

还需要将 \(i\) 全部 \(+1\) 才能保证 \(MEX\)\(i\)(该过程代价不计入变量 \(ans\)

总代价即为 \(ans'+(i-1)-stack[top]+cnt[i]\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2e5 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
int a[N], stack[N];
void solve() {
	int n = read(), top = 0, ans = 0, cnt[N] = {0};
	for(int i = 1; i <= n; ++ i) cnt[a[i] = read()] ++;
	printf("%lld ", cnt[0]);
	for(int i = 1; i <= n; ++ i) {
		while(cnt[i - 1] --> 0) stack[++ top] = i - 1;
		if(top == 0) {
			for(int j = i; j <= n; ++ j) printf("-1 ");
			break;
		}
		ans += i - 1 - stack[top --];
		printf("%lld ", ans + cnt[i]);
	}
	puts("");
}
signed main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

F. Let's Play the Hat?(⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/F

题目大意:

\(n\) 个人,分成 \(m\) 组,共分 \(k\) 次组

对于每次分组,分组成员数为 \(\lfloor\frac nm\rfloor\)\(\lceil\frac nm\rceil\)

设成员 \(i\) 被分到成员数为 \(\lceil\frac nm\rceil\) 组的次数为 \(b_i\)

求任意合法分组方案,满足 \(|b_i-b_j|\leq 1\)

输出时每组样例中以空行隔开

每组样例输出 \(m\) 行,每行首个数为该组成员数 \(x\),随后 \(x\) 个数表示成员编号

\(T\) 组数据

\(1\leq T\leq 10^4,2\leq n\leq2\cdot10^5,1\leq m\leq\lfloor\frac n2\rfloor,1\leq k\leq 10^5\)

保证所有测试样例 \(\sum n\cdot k\leq 2\cdot 10^5\)

题解:模拟

要求 \(|b_i-b_j|\leq 1\),即每次分人数为 \(\lceil\frac nm\rceil\) 的组时,都按编号次序进行分组

分人数为 \(\lfloor\frac nm\rfloor\) 的组时,将其他未被分组的人依次加入即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
void solve() {
	int n = read(), m = read(), k = read();
	int val = 0;
	for(int op = 1; op <= k; op ++) {
		for(int i = 1; i <= (n % m); ++ i) {
			printf("%d ", n / m + 1);
			for(int j = 1; j <= n / m + 1; ++ j) {
				printf("%d ", ++ val);
				if(val == n) val = 0;
			}
			puts("");
		}
		int tmp = val;
		for(int i = 1; i <= (m - n % m); ++ i) {
			printf("%d ", n / m);
			for(int j = 1; j <= n / m; ++ j) {
				printf("%d ", ++ tmp);
				if(tmp == n) tmp = 0;
			}
			puts("");
		}
	}
	puts("");
}
int main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

G. Unusual Minesweeper(⭐⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/G

题目大意:

给定 \(n\)\(k\),表示有 \(n\) 个炸弹,

且每个炸弹能同时引爆与该炸弹相同行或相同列坐标差 \(\leq k\) 的炸弹

对于每个炸弹,给出坐标 \((x,y)\) 和寿命 \(tim\)(单位 :\(s\)

炸弹寿命结束后自动引爆

每秒时,可选择引爆一个炸弹(\(t\in[0,\infin]\)

若时间为 \(t\) 时能够引爆所有炸弹,求 \(t\) 的最小值

\(T\) 组数据

\(1\leq T\leq 10^4,1\leq n\leq 2\cdot10^5,0\leq k\leq 10^9\)

\(-10^9\leq x,y\leq 10^9,0\leq tim\leq 10^9\)

题解:并查集,贪心

并查集维护可同时被引爆的炸弹的集合

并通过 每个炸弹集合中的保质期 维护每个集合的自动引爆时间

则问题可以简化为对于 \(cnt\) 个炸弹,每个炸弹有一个自动引爆时间

求最快的引爆方法

利用贪心,首先将自动引爆时间从大到小排序

从前往后依次手动引爆,从后往前为自动引爆,求解答案 \(t\)

枚举所有炸弹都被引爆的时间,若仍未被手动引爆的炸弹已全部被手动引爆

该时间即为最快引爆时间

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2e5 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
int n, k, fa[N], tim[N], tmp[N];
struct Node {
	int x, y, id;
} e[N];
bool cmp(Node a, Node b) {
	return (a.x == b.x) ? (a.y < b.y) : (a.x < b.x);
}
int find(int x) { return (x == fa[x]) ? x : fa[x] = find(fa[x]); }
void Merge() {
	sort(e + 1, e + n + 1, cmp);
	for(int i = 2; i <= n; ++ i) {
		if(e[i].x == e[i-1].x && e[i].y - e[i-1].y <= k) {
			int x = e[i].id, y = e[i-1].id;
			int fx = find(x), fy = find(y);
			tim[fy] = min(tim[fx], tim[fy]);
			fa[fx] = fy; 
		}
	}
}
bool comp(int x, int y) { return x > y; }
void solve() {
	n = read(); k = read();
	for(int i = 1; i <= n; ++ i) {
		e[i].x = read(); e[i].y = read(); 
		tim[i] = read(); e[i].id = fa[i] = i;
	}
	Merge();
	for(int i = 1; i <= n; ++ i) swap(e[i].x, e[i].y);
	Merge();
	int cnt = 0;
	for(int i = 1; i <= n; ++ i) {
		if(find(i) == i) tmp[++ cnt] = tim[i];
	}
	sort(tmp + 1, tmp + cnt + 1, comp);
	for(int i = 0; i <= cnt - 2; ++ i) {
		if(tmp[i + 2] <= i) return printf("%lld\n", i), (void)0;
	}
	printf("%lld\n", cnt - 1);
}
signed main() {
	int T = read();
	while(T --> 0) solve(); 
	return 0;
}

H. Permutation and Queries(⭐⭐⭐)

题目链接:https://codeforces.com/contest/1619/problem/H

题目大意:

\(n\) 个元素的排列 \(p\),进行 \(q\) 次操作

两种操作类型

\(1\ x\ y:\) 交换 \(p_x\)\(p_y\)

\(2\ i\ k:\) 输出 \(p_{p_{\cdots_{p_i}}}\)\(k\)\(p\)

\(1\leq n,q\leq 10^5,1\leq x,y\leq n,x\neq y,1\leq i,k\leq n\)

题解:分块

考虑暴力复杂度,对于交换 \(O(1)\),输出 \(O(n)\)

利用 分块 降低输出的复杂度,维护 \(nxt,pre,leap\) 数组

\(nxt[i]=p[i]\)

\(pre[j]=i\)\(nxt[i]=j\)

\(leap[i]=p_{p_{\cdots_{p_i}}}\)\(300(\approx\sqrt{n})\)\(p\)

对于操作 \(1\),维护 \(nxt,pre,leap\) 数组即可

对于操作 \(2\),先跳 \(leap\),再跳 \(nxt\) 即可

时间复杂度:\(O((n+q)\displaystyle \sqrt{n}).\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
typedef long long LL;
int read() {
	int x = 0, f = 1; char ch;
	while(! isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48));
	return x * f;
}
template <class T> T Max(T a, T b) { return a > b ? a : b; }
template <class T> T Min(T a, T b) { return a < b ? a : b; }
int nxt[N], pre[N], leap[N];
void Swap(int x, int y) {
	swap(nxt[x], nxt[y]);
	int to = x;
	for(int i = 1; i <= 300; ++ i) to = nxt[to];
	for(int i = 1; i <= 300; ++ i) {
		leap[x] = to;
		x = pre[x]; to = pre[to];
	}
	to = y;
	for(int i = 1; i <= 300; ++ i) to = nxt[to];
	for(int i = 1; i <= 300; ++ i) {
		leap[y] = to;
		y = pre[y]; to = pre[to];
	}
}
int Calc(int i, int y) {
	while(y >= 300) i = leap[i], y -= 300;
	while(y) i = nxt[i], y --;
	return i;
}
int main() {
	int n = read(), q = read();
	for(int i = 1; i <= n; ++ i) nxt[i] = read(), pre[nxt[i]] = i;
	for(int i = 1, to; i <= n; ++ i) {
		to = i;
		for(int j = 1; j <= 300; ++ j) to = nxt[to];
		leap[i] = to;
	}
	for(int i = 1, op, x, y; i <= q; ++ i) {
		op = read(); x = read(); y = read();
		if(op == 1) Swap(x, y);
		if(op == 2) printf("%d\n", Calc(x, y));
	}
	return 0;
}
posted @ 2021-12-26 01:02  计网君  阅读(306)  评论(0)    收藏  举报