比赛链接:

https://codeforces.com/contest/1619

A. Square String?

题目大意:

A string is called square if it is some string written twice in a row.
For a given string s determine if it is square.

思路:

首先字符串长度要是偶数,然后直接循环判断字符串前半段和后半段是否相等就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
const int mod = 1;
LL T, n;
string s;
void solve(){
	cin >> s;
	int f = 1;
	if (s.length() % 2 != 0){
		cout << "NO\n";
		return;
	}
	for (int i = 0; i < s.length() / 2; i++){
		if (s[i] != s[i + s.length() / 2]){
			f = 0;
			break;
		}
	}
	cout << (f ? "YES\n" : "NO\n");
}
int main(){
	cin >> T;
	while(T--)
		solve();
	return 0;
}

B. Squares and Cubes

题目大意:

计算 1 到 \(n\)平方数立方数的个数。

思路:

数据范围只有 1e9,直接暴力跑出来 1 到 \(n\)平方数立方数放到 \(set\) 里面就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int mod = 1;
int T, n;
void solve(){
	cin >> n;
	set <int> a;
	for (int i = 1; i * i <= n; i++)
		a.insert(i * i);
	for (int i = 1; i * i * i <= n; i++)
		a.insert(i * i * i);
	cout << a.size() << "\n";
}
int main(){
	cin >> T;
	while(T--)
		solve();
	return 0;
}

C. Wrong Addition

题目大意:

两个数相加,首先将长度较短的数补上前导零,使长度相等。然后从右至左对每一位做正常加法,得出来的数直接写到结果里面。
题目中的例子

6 + 5 = 11,直接就写到结果中去了。
现给出 \(a\)\(s\),要求找到一个 \(b\),按照上述加法,使 \(a + b = s\),找不到输出 -1.

思路:

直接将 \(a\)\(s\) 的最后一位拿出来计算,分别记为 \(x 和 y\)
\(x <= y\) 时,显然 \(b\) 的最后一位就是 \(y - x\)
\(x > y\) 时,说明 \(a 和 b\) 某位相加之后大于 10,那么我们再取 \(s\) 的下一位,\(y\) 就变成这个两位数,然后判断这个两位数是不是 >= 10 并且 <= 18(两个一位数相加,肯定 <= 18 的),若是,说明 \(b\) 的这一位也是 \(y - x\),否则说明 \(b\) 不存在。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
const int mod = 1;
LL T, n, a, s;
void solve(){
	cin >> a >> s;
	vector <int> b;
	while (s){
		int x = a % 10, y = s % 10;
		if (x <= y) b.emplace_back(y - x);
		else {
			s /= 10;
			y += 10 * (s % 10);
			if (x < y && y >= 10 && y <= 18) b.emplace_back(y - x);
			else {
				cout << "-1\n";
				return;
			}
		}
		a /= 10;
		s /= 10;
	}
	if (a) cout << "-1\n";
	else {
		while (b.back() == 0) b.pop_back();
		for (int i = b.size() - 1; i >= 0; i--)
			cout << b[i];
		cout << "\n";
	}
}
int main(){
	cin >> T;
	while(T--)
		solve();
	return 0;
}

D. New Year's Problem

题目大意:

\(Vlad\)\(n\) 个朋友,他打算给每位朋友买一个新年礼物,现在有 \(m\) 个商店,在第 \(i\) 个商店买给第 \(j\) 个朋友的礼物会给这个朋友带来 \(p[i][j]\) 个快乐值,他最多只能去 \(n - 1\) 个商店买礼物,他想知道朋友中快乐值的最小值最大是多少。

思路一:

如果最大值能是 \(x\),说明 \(x\) - 1 也能实现,而当 \(x\) 不能时,\(x\) + 1 也不能,所以想到二分去猜最大值。
猜最大值为 \(x\),要实现最大值是 \(x\) 的话,需要满足两个条件:
首先,每个朋友至少能获得一个带来 \(x\) 及以上快乐值的玩具。
其次,因为只去了 \(n\) - 1 个商店,而我们要买 \(n\) 个商品,所以有一个商店买了两个玩具,即某个商店中有两个 >= \(x\) 的快乐值的玩具。

代码:

#include <bits/stdc++.h>
using namespace std;
int T = 1, n, m;
vector < vector <int> > p;
bool judge(int x){
	vector<bool> v(n + 1);
	bool f = false;
	for (int i = 1; i <= m; i++){
		int c = 0;
		for (int j = 1; j <= n; j++)
			if (p[i][j] >= x){
				v[j] = true;
				c++;
			}
		if (c > 1) f = true;
	}
	if (!f && n > 1) return false;
	for (int i = 1; i <= n; i++)
		if (!v[i]) return false;
	return true;
}
void solve() {
	scanf("%d %d", &m, &n);
	p.assign(m + 1, vector<int> (n + 1));
	for (int i = 1; i <= m; i++)
		for (int j = 1; j <= n; j++)
			scanf("%d", &p[i][j]);
	int l = 1, r = 1e9 + 5;
	while (l < r){
		int mid = (l + r) >> 1;
		if (judge(mid)) l = mid + 1;
		else r = mid;
	}
	cout << l - 1 << "\n";
}
int main(){
	cin >> T;
	while (T--)
		solve();
	return 0;
}

思路二:

直接贪,因为一定有个商店买了两个玩具,所以我们先去玩具带来的快乐值中次大值最大的那个商店,将这个玩具送给朋友,然后我们再买其他人的玩具。求出最小值,就是答案。

代码:

#include <bits/stdc++.h>
using namespace std;
#define all(x) (x).begin(), (x).end()
int T = 1, n, m;
void solve() {
	scanf("%d %d", &m, &n);
	int ans = 0;
	vector <vector<int>> p(m, vector <int>(n));
	for (int i = 0; i < m; i++)
		for (int j = 0; j < n; j++)
			scanf("%d", &p[i][j]);
	for (int i = 0; i < m; i++){
		vector <int> t = p[i];
		sort(all(t), greater<int>());
		ans = max(ans, t[1]);
	}
	for (int j = 0; j < n; j++){
		int mx = 0;
		for (int i = 0; i < m; i++)
			mx = max(mx, p[i][j]);
		ans = min(ans, mx);
	}
	cout << ans << "\n";
}
int main(){
	cin >> T;
	while (T--)
		solve();
	return 0;
}

E. MEX and Increments

题目大意:

给定一个长为 \(n\) 的非负整数序列,每一步操作中可以任选一个 \(j\),使 \(a_j\) +1,求使 \(MEX = i\)\(i = 0, 1, 2... n\)) 的最小操作次数,若无法实现,输出 -1。(\(MEX\) 即序列中未出现的非负整数)

思路:

要使 \(MEX\) = \(i\),就要使 0 到 \(i - 1\) 全部都存在,同时将所有的 \(i\) 都增大。因为要求最小的步数,那么最贪的方式就是使 \(i\) 增大 1。
我们可以发现使序列的 \(MEX\) 等于 \(i\) 的这个状态不会对后面造成影响,同时,它也可以从前一个状态转移过来,我们可以在通过最小的操作次数使 \(MEX\) 等于 \(i - 1\) 的基础上,再用最小的操作次数使 \(MEX\) 等于 \(i\)
所以对于 \(i\) 的状态,我们只需要考虑序列中等于 \(i - 1\) 的数是不是 0 即可。
如果是 0,我们就要从前面多余的数中取一个去补上,如果没有多余的数,那后面所有的都是 -1。
如果不是 0,那就不用再操作前面的数,只需要将所有等于 \(i\) 的数 +1 就好了。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
int T = 1, n;
void solve(){
	scanf("%d", &n);
	vector <int> a(n), cnt(n + 1);
	for (int i = 0; i < n; i++){
		scanf("%d", &a[i]);
		cnt[a[i]]++;
	}
	sort(a.begin(), a.end());
	stack <int> s;
	LL sum = 0;
	vector <LL> ans(n + 1, -1);
	ans[0] = cnt[0];
	for (int i = 1; i <= n; i++){
		if (cnt[i - 1] == 0){
			if (s.empty()) break;
			sum += i - 1 - s.top();
			s.pop();
		}
		ans[i] = sum + cnt[i];
		while (cnt[i - 1] > 1){
			cnt[i - 1]--;
			s.push(i - 1);
		}
	}
	for (int i = 0; i <= n; i++)
		cout << ans[i] << " \n"[i == n];
}
int main(){
	cin >> T;
	while(T--)
		solve();
	return 0;
}

F. Let's Play the Hat?

题目大意:

\(n\) 个人在 \(m\) 张桌子上玩 \(k\) 场游戏,每场游戏中坐在每张桌子的人数为 \(\lceil \frac{n}{m} \rceil\)\(\lfloor \frac{n}{m} \rfloor\),输出一种方案,使得任何两人坐在人数为 \(\lceil \frac{n}{m} \rceil\) 的次数差不超过 1。

思路:

\(i\) 轮坐在大桌子上的人,下一轮肯定要尽可能坐在小桌子上,这样子才能让每个人坐在大桌子上的次数趋向平衡。
所以考虑一个循环,设 \(c\) 为大桌子上的人数,\(f\) 为小桌子上的人数,\(res\) 为大桌子的数量,人的序号从 0 开始。
将坐在小桌子上的人按照顺序向前移动,即每一轮之后 \(a[i] = a[(i + res * f) % n]\)
循环输出就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
int T, n, m, k;
void solve(){
	cin >> n >> m >> k;
	vector <int> a(n), t(n);
	for (int i = 0; i < n; i ++ )
		a[i] = i + 1;
	int c = (n + m - 1) / m, f = n / m, res = n % m;
	for (int i = 1; i <= k; i ++ ){
		for (int j = 0; j < res; j ++ ){
			cout << c << " ";
			for (int u = j * c; u < (j + 1) * c; u ++ )
				cout << " " << a[u];
			cout << "\n";
		}
		for (int j = 0; j < m - res; j ++ ){
			cout << f << " ";
			for (int u = res * c + j * f; u < res * c + (j + 1) * f; u ++ )
				cout << " " << a[u];
			cout << "\n";
		}
		t = a;
		for (int j = 0; j < n; j ++ )
			a[j] = t[( j + res * c ) % n];
	}
	cout << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> T;
	while ( T -- )
		solve();
	return 0;
}

G. Unusual Minesweeper

题目大意:

已知 \(n\) 颗地雷的坐标以及它们爆炸的时间(从 0 开始),每一颗地雷爆炸,会引爆垂直和水平(两条垂线)的距离小于等于 \(k\) 的所有地雷,每一秒可以主动引爆一颗地雷(不管它本来是什么时候引爆的),问最少多少秒使所有地雷爆炸。

思路:

一颗地雷引爆后可能会导致很多地雷引爆,通过并查集将一起爆炸的地雷连通,同时可以计算出这一个块最小的爆炸时间。
然后遍历每一个块,找到使得所有地雷引爆的最小时间。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int T, n, k, fa[N];
int find(int x){
	if (fa[x] == x) return x;
	return fa[x] = find(fa[x]);
}
void join(int a, int b){
	int aa = find(a), bb = find(b);
	if (aa == bb) return;
	fa[bb] = aa;
}
void solve(){
	cin >> n >> k;
	vector <int> x(n), y(n), t(n), p(n), a(n, 1e9 + 1), b;
	iota(p.begin(), p.end(), 0);
	iota(fa, fa + n, 0);
	for (int i = 0; i < n; i ++ )
		cin >> x[i] >> y[i] >> t[i];
	sort(p.begin(), p.end(), [&](int i, int j){
		if (x[i] != x[j]) return x[i] < x[j];
		return y[i] < y[j];
	});
	for (int i = 0; i < n - 1; i ++ ){
		int a = p[i], b = p[i + 1];
		if (x[a] == x[b] && y[a] + k >= y[b])
			join(a, b);
	}
	sort(p.begin(), p.end(), [&](int i, int j){
		if (y[i] != y[j]) return y[i] < y[j];
		return x[i] < x[j];
	});
	for (int i = 0; i < n - 1; i ++ ){
		int a = p[i], b = p[i + 1];
		if (y[a] == y[b] && x[a] + k >= x[b])
			join(a, b);
	}
	for (int i = 0; i < n; i ++ ){
		int u = find(i);
		a[u] = min(a[u], t[i]);
	}
	for (int i = 0; i < n; i ++ )
		if (find(i) == i)
			b.push_back(a[i]);
	sort(b.begin(), b.end());
	int ans = b.size() - 1;  //引爆所有地雷所花的时间(时间从 0 开始计算)
	for (int i = 0; i < b.size(); i ++ )
		ans = min(ans, max(b[i], (int)b.size() - i - 2));  //该地雷及之前的地雷自然引爆,后面的地雷手动引爆
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> T;
	while (T -- )
		solve();
	return 0;
}

H. Permutation and Queries

题目大意:

给定一个长为 \(n\) 的排列,\(q\) 次询问:
1、交换两个位置的数,\(p_x\)\(p_y\)
2、输出进行 \(k\)\(i = p_i\) 操作后的结果。

思路:

每个数的变化最多是一个长为 \(n\) 的环,直接暴力操作 \(k\) 次肯定超时。
通过分块的思想,预处理每个数操作 \(\sqrt{n}\) 次后的结果,同时建一个链表,存下来每个数前 \(\sqrt{n}\) 和后 \(\sqrt{n}\) 的数。
每次交换过后,要更新链表。
每次查询的时候,以 \(\sqrt{n}\) 的步长来找即可。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, q, op, x, y, pre[N], to[N], f[N];
void link(int x){
	int y = x;
	for (int i = 1; i <= m; i ++ )
		y = to[y];
	for (int i = 1; i <= m; i ++ ){
		f[x] = y;
		x = pre[x];
		y = pre[y];
	}
}
void update(int x, int y){
	int a = to[x], b = to[y];
	to[x] = b, to[y] = a;
	pre[b] = x, pre[a] = y;
	link(x), link(y);
}
int query(int x, int y){
	int t = 0;
	while (t + m <= y){
		x = f[x];
		t += m;
	}
	while (t < y){
		t ++ ;
		x = to[x];
	}
	return x;
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> n >> q;
	m = sqrt(n);
	for (int i = 1; i <= n; i ++ ){
		cin >> to[i];
		pre[to[i]] = i;
	}
	for (int i = 1; i <= n; i ++ )
		if (!f[i])
			link(i);
	while (q -- ){
		cin >> op >> x >> y;
		if (op == 1) update(x, y);
		else cout << query(x, y) << "\n";
	}
	return 0;
}
posted on 2022-01-23 20:28  Hamine  阅读(44)  评论(0编辑  收藏  举报