Codeforces Round #590 (Div. 3) 题解

A  . Equalize Prices Again

https://codeforces.com/contest/1234/problem/A

相加取平均,向上取整。。

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
 
int main() {
	int t;
	cin >> t;
	while (t--) {
		int n; cin >> n;
		int sum = 0;
		for (int i = 1; i <= n; i++) {
			int inp; scanf("%d", &inp);
			sum += inp;
		}
		double res = (double)sum / n;
		cout << (int)ceil(res) << endl;
	}
	return 0;
}

 

B. Social Network

https://codeforces.com/contest/1234/problem/B2

离散化,然后手写个队列模拟即可

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
map<int, int> M;
const int maxn = 2e5 + 7;
int q[maxn], l, r, tot, flag[maxn];
int main() {
	int n, k;
	cin >> n >> k;
	l = 1, r = 0;
	for (int i = 1; i <= n; i++) {
		int inp;
		scanf("%d", &inp);
		if (!M.count(inp)) M[inp] = ++tot;
		if (flag[M[inp]]) continue;
		else {
			if (r - l >= k-1) {
				flag[M[q[l++]]] = 0;
				q[++r] = inp;
				flag[M[inp]] = 1;
			}
			else {
				q[++r] = inp;
				flag[M[inp]] = 1;
			}
		}
	}
	cout << (r - l + 1) << endl;
	for (int i = r; i >= l; i--) cout << q[i] << " ";
	return 0;
}

 

C. Pipes

https://codeforces.com/contest/1234/problem/C

注意到 1,2号是一样的,3,4,5,6号是一样的

我就用1号表示1,2号水管, 用2号表示3,4,5,6号水管

可以用一个坐标模拟左上角的水流

注意到假如出现2号,那么它的另外一侧就一定也要是2号

然后写个模拟就可以了

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
map<int, int> M;
const int maxn = 2e5 + 7;
int num[3][maxn];
int main() {
	int t; cin >> t;
	string inp1;
	while (t--) {
		int n; cin >> n;
		for (int i = 0; i <= 1; i++) {
			cin >> inp1;
			for (int j = 1; j <= n; j++) {
				num[i][j] = inp1[j - 1] - '0';
				if (num[i][j] <= 2) num[i][j] = 1;
				else num[i][j] = 2;
			}
		}
		int x = 0, y = 1; //这个表示当前的坐标
		int ok = 1;
		while (y != n + 1) {
			if (num[x][y] == 2 && num[x ^ 1][y] == 2) { //假如都是二号
				x ^= 1;
				y++;
			}
			else if (num[x][y] == 1) //假如是一号就前进
				y++;
			else { 
				ok = 0;
				break;
			}
		}
		if (ok == 0||(y==n+1&&x==0)) {
			puts("NO");
		}
		else {
			puts("YES");
		}
	}
	return 0;
}

 

 

D. Distinct Characters Queries

https://codeforces.com/contest/1234/problem/D

很明显的线段树,将26个字母用二进制表示,线段树则维护这个二进制

因为只有26个字母,所以线段树的节点合并的时候是常数级别合并

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 7;
int c[maxn<<4];
string inp;
int id = -1;
void build_tree(int l,int r,int ind) {
	if (l == r) {
		c[ind] = (1 << (inp[++id] - 'a'));
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(l, mid, ind << 1);
	build_tree(mid + 1, r, ind << 1 | 1);
	c[ind] = c[ind << 1] | c[ind << 1 | 1];
}
void update(int l, int r, int aim, int val, int ind) {
	if (l == r) {
		c[ind] = (1 << val);
		return;
	}
	int mid = (l + r) >> 1;
	if (aim <= mid) update(l, mid, aim, val, ind << 1);
	else update(mid + 1, r, aim, val, ind << 1 | 1);
	c[ind] = c[ind << 1] | c[ind << 1 | 1];
}
int query(int l, int r, int q_l, int q_r, int ind) {
	if (q_l <= l && q_r >= r) {
		return c[ind];
	}
	int mid = (l + r) >> 1;
	int res1, res2;
	res1 = res2 = 0;
	if (q_l <= mid) res1 = query(l, mid, q_l, q_r, ind << 1);
	if (q_r > mid) res2 = query(mid + 1, r, q_l, q_r, ind << 1 | 1);
	return res1 | res2;
}
int main() {
	cin >> inp;
	int n = inp.size();
	build_tree(1, n, 1);
	int q; cin >> q;
	int opt, in1, in2;
	char s[3];
	while (q--) {
		scanf("%d %d", &opt, &in1);
		if (opt == 1) {
			scanf("%s", s);
			update(1, n, in1, s[0] - 'a', 1);
		}
		else {
			scanf("%d", &in2);
			int res, cnt = 0;
			res = query(1, n, in1, in2, 1);
			for (int i = 0; i < 26; i++)
				if ((res >> i) & 1) cnt++;
			printf("%d\n", cnt);
		}
	}
	return 0;
}

 

E. Special Permutations

https://codeforces.com/contest/1234/problem/E

可以看到:

第i个变化为第i+1个的时候,交换的是第1个和第i+1个的坐标

每一次只变化2个数,因此每次只要找这两个数在x中前后的位置的数,作相应的修改就行了

所以用vector数组保存每个q[i]的值对应的所有在q[i]的下标

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 7;
int q[maxn];
vector<int> v[maxn];
int n, m;
ll res[maxn];
int main() {
	cin >> n >> m;
	int sto = 0;
	for (int i = 1; i <= m; i++) {
		scanf("%d", q + i);
		v[q[i]].push_back(i); // v存的是q[i]这个值在Q中的下标
	}
	for (int i = 2; i <= m; i++)
		res[1] += 1LL * abs(q[i] - q[i - 1]);
	for (int i = 2; i <= n; i++) {
		res[i] = res[i - 1];
		for (int j = 0; j < v[i - 1].size(); j++) { 
			for (int k = v[i - 1][j] - 1; k <= v[i - 1][j] + 1; k += 2) {//寻找q[i]前后两个数,并且计算
				if (k <= 0 || k > m || q[k] == i || q[k] == i - 1) continue;
				res[i] -= (ll)q[k];
				if (q[k] > i - 1) res[i] += 1LL;
				res[i] += (ll)abs(i - 1 - q[k]);
				if (q[k] > i - 1) res[i] -= 1LL;
			}
		}
		for (int j = 0; j < v[i].size(); j++) {
			for (int k = v[i][j] - 1; k <= v[i][j] + 1; k += 2) {
				if (k <= 0 || k > m || q[k] == i || q[k] == i - 1) continue;
				res[i] -= (ll)abs(q[k] - i);
				if (q[k] < i) res[i] += 1LL;
				res[i] += (ll)q[k];
				if (q[k] > i) res[i] -= 1LL;
			}
		}
	}
	for (int i = 1; i <= n; i++) cout << res[i] << " ";
	return 0;
}

 

 

F. Yet Another Substring Reverse

https://codeforces.com/contest/1234/problem/F

这个我竟然没想到状压dp  orz。。。

可以选择(或者不选)一段字符串逆转,使得最后那段连续的每个字符都不一样的子串最长

很明显一段字符串可以通过逆转到达原字符串的任意位置,因此题目就变为:

给一段字符串,让你找到里面的两段连续的子字符串,它们的每个字符都不一样,并且加起来的长度最长

题目说字符的数量小于等于20个,2^20大概是(10^6~10^7),据说这里是在疯狂暗示 状压+子集遍历

对于每个连续的子串我都可以其压缩为一个十进制数,那么这里就有一个想法:

用dp[x]表示 被压缩成x的子串的字符个数 ,比如 abc,可以被压缩成 111(二进制),则dp[7]=3

假如我们能够处理出这样的dp数组,就是对原字符串所有的合法子串都用一个十进制数表示(合法子串指:连续+每个字符都不同)

对于某个二进制数:

假如它是合法的,所以具有dp的值,且表示原字符串有一段可以被压缩成这样的子串

寻找它的所有补集的子集:

那么就可以选出答案

遍历完所有状态,再遍历每个状态的补集的子集,就能得到答案。

但是这样是超时的,因为时间只允许我们遍历一次子集,上面的方法是: 遍历所有的子集,对于每一个子集遍历其所有补集的子集,这样就遍历了两次子集

因此我们dp[x]不表示状态为x的时候的字符数,而是表示状态为x的所有子集的字符数的最大值,预处理出这样的dp数组,就可以只遍历一次子集,具体看代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1 << 20;
int vis[22];
int dp[maxn];
int main() {
	string s; cin >> s;
	for (int i = 0; i < s.size(); i++) { //这里的dp[x]表示状态为x的字符数
		memset(vis, 0, sizeof(vis));
		int num = 0, cnt = 0;
		for (int j = i; j < s.size(); j++) {
			if (vis[s[j] - 'a'])
				break;
			vis[s[j] - 'a'] = 1;
			num |= (1 << (s[j] - 'a'));
			cnt++; //cnt:字符数
			dp[num] = cnt;
		}
	}
	for (int i = 1; i <= (1 << 20); i++) {//这里的dp[x]表示状态为x以及x的子集的最大字符数
		for (int j = 0; j < 20; j++) {
			if (i&(1 << j)) { 
				dp[i] = max(dp[i], dp[i ^ (1 << j)]);
			}
			else if (i < (1 << j))
				break;
		}
	}
	int ans = 0;
	for (int i = 1; i < (1 << 20); i++) { //只需要遍历一次子集即可得到答案
		ans = max(dp[i] + dp[i ^ ((1 << 20) - 1)], ans);
	}
	cout << ans << endl;

}

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值