AtCoder Beginner Contest 390 题解ABCDEF

A - 12435

题意:给出一个整数序列 A,A 里包含 5 个数,肯定是 1∼5 的一种排列。问能不能进行且只进行对相邻两个数的 1 次交换,让这个序列变为升序的。如果能,输出 Yes,否则输出 No

思路:两两比较模拟即可

	int a[6];
	for(int i = 1;i<=5;i++){
		cin >> a[i];
	}
	int cnt = 0;
	for(int i = 1;i<=4;i++){
		if(a[i] > a[i+1]){
			swap(a[i],a[i+1]);
			cnt++;
		}
	}
	if(cnt == 1)cout <<"Yes\n";
	else cout << "No\n";

B - Geometric Sequence

题意:给定一个正整数序列 A,问这个序列 A 是否为等比数列。如果是,输出 Yes ,否则输出 No

思路:模拟即可

	int n;
	cin >> n;
	vector<LDB> q(n+1);
	for(int i = 1;i<=n;i++)cin >> q[i];
	LDB st = q[2] / q[1];
	for(int i =2;i<=n-1;i++){
		if(q[i+1]/q[i] != st){
			cout <<"No\n";
			return 0;
		}
	}
	cout << "Yes\n";

C - Paint to make a rectangle

题意:

给你一个行数为 H 列数为 W 的网格。
让 (i,j) 表示从顶端起位于第 i 行 (1≤i≤H) 和第 j 列 (1≤j≤W) 的单元格。(1≤j≤W) 的单元格。
网格的状态由长度为 W 的 H 字符串 S1​,S2​,…,SH​ 表示,如下所示:

  • 如果 Si​ 的 j -th 字符是 "#",则单元格 (i,j) 被涂成黑色。
  • 如果 Si​ 的 j -th 字符是 .,则单元格 (i,j) 被涂成白色。
  • 如果 Si​ 的 j -th 字符是 ?,则单元格 (i,j) 尚未涂黑。

高桥希望将每个尚未涂色的单元格涂成白色或黑色,这样所有黑色单元格就组成了一个矩形。
更确切地说,他希望存在一个四元整数 (a,b,c,d)(1≤a≤b≤H,1≤c≤d≤W),使得:

对于每个单元格 (i,j)(1≤i≤H,1≤j≤W),如果有 a≤i≤b 和 c≤j≤d,则该单元格为黑色; 否则,单元格为白色。

确定这是否可能。

思路:按照题目要求,在左上到右下的#里面不能出现.,记录一下最长出现区间然后模拟即可,N<1000所以On^2完全能过通过

	int H , W;
	cin >> H >> W;
	int L = INF , R = -INF , U = -INF, D = INF;
	vector<vector<char>> g(M,vector<char>(M));
	for(int i = 1;i<=H;i++){
		for(int j = 1;j<=W;j++){
			cin >> g[i][j];
			if(g[i][j] == '#'){
				L = min(L , j);
				R = max(R , j);
				U = max(U , i);
				D = min(D , i);
			}
		}
	}
	bool f = true;
	for(int i = D ;i<=U;i++){
		for(int j = L;j<=R;j++){
			if(g[i][j] == '.'){
				f = false;
			}
		}
	}
	if(f)cout <<"Yes\n";
	else cout <<"No\n";

D - Stone XOR

首先关于异或我们要知道几个性质

假如xor_val为一个数组所有数的异或值和,那么想移除一个a[i]只需要 xor_val ^= a[i]即可在xor_val里面移除a[i]

题意:小 ψ 有 N(N≤12)个袋子,每个袋子里面有一些石子,小 ψ 每次可以选择两个袋子,把一个袋子里的全部石子倒入另一个袋子中。求进行若干次操作后,所有袋子里的石子数量的异或和的可能性数量。

思路:根据xor的性质,我们发现:终异或和的值仅与分组方式有关。每次合并操作相当于将两个袋子的石子合并为一个新的袋子,最终的分组可以看作将初始的 N 个袋子划分成若干非空子集,每个子集的石子总和构成最终的异或和。

显然想到dfs

我们

用两个变量表示当前 DFS 的状态:

  • deep:当前正在处理第几个石子(石子的下标从 1 开始,到 n)。
  • now:当前已经形成的分组(也就是已经开始的袋子)的数量。

对于第 deep 个石子,我们有两个选择:

1.放入已有的某个组中:也就是将石子加入到之前已经创建好的袋子中。

2.新建一个组:把石子单独作为一个新袋子的开始,也相当于执行了一次“合并”操作,从而增加了组数。
因此,循环变量 i 从 1 到 now+1

  • 当 i≤now时,将第 deep 个石子加到已有的第 i 个组中;
  • 当 i=now+1 时,表示新建一个组,将石子放进去,组数增加1。
// Code Start Here	
	constexpr int N = 15;
	int n;
	cin >> n;
	vector<int> a(N);
	vector<int> vec(N);
	unordered_set<int> st;
	for(int i = 1;i<=n;i++){
		cin >> a[i];
	}
	auto dfs = [&](auto &self,int deep,int now)->void{
		if(deep == n + 1){
			int sum = 0;
			for(int i = 1;i<=now;i++){
				sum ^= vec[i];
			}
			st.insert(sum);
			return ;
		}
		for(int i = 1;i<=now + 1;i++){
			vec[i] += a[deep];
			if(i == now+1)self(self,deep+1,now+1);
			else self(self,deep + 1,now);
			vec[i] -= a[deep];
		}
	};
	dfs(dfs,1,0);
	cout << sz(st) << endl;

一些题外话:这个题有的dfs看脸,心情好能通过,心情不好通过不了,笔者第一次写的dfs就是这样,第二次优化了以下跑过了,有点搞笑

E - Vitamin Balance

题意:

小 γ 有 N 个食物,吃掉第 i 个食物会获取 Ai​ 单位的维生素 Vi​(其中 Vi​∈{1,2,3}),还会获取 Ci​ 单位卡路里。

小 γ 不想变得太胖,这样就很难抓住老鼠了,所以小 γ 摄取的总卡路里不能超过 X 单位。

小 γ 还希望营养均衡,所以他希望你能求出在所有情况下三种维生素摄入量最小值的最大值。

思路:

把三种维生素分类,跑三次 01 背包,然后枚举两个维生素分配的卡路里数,另一个的卡路里数量也就确定了

// Code Start Here	
	int n , x;
	cin >> n >> x;
	vector<int> v(n+1),a(n+1),c(n+1);
	vector<int> dp1(x+1,0),dp2(x+1,0),dp3(x+1,0);
	for(int i = 1;i<=n;i++)cin >> v[i] >> a[i] >>c[i];
	int ans = 0;
	auto check = [&](vector<int> &dp,int op)->void{
		for(int i = 1; i <= n; ++i) {
			if(v[i] != op) continue;
			for(int j = x; j >= c[i]; --j)
				dp[j] = max(dp[j], dp[j - c[i]] + a[i]);
		}	
	};
	check(dp1,1);
	check(dp2,2);
	check(dp3,3);
	for(int i = 1; i <= x; ++i)
		for(int j = 1; j <= x - i; ++j) {
			int k = x - i - j;
			ans = max(ans, min(min(dp1[i], dp2[j]), dp3[k]));
	}
	cout << ans << endl;
	return 0;

若爆内存请检查dp数组开的长度,若答案错误请注意lambda的传导方式

F - Double Sum 3

题意:

给你一个长为 N 的整数序列 A=(A1​,A2​,…AN​)。
定义 f(L,R) 如下

  • 从一块空黑板开始。在黑板上依次写下 R−L+1 个整数 AL​,Al+1​,…,AR​
  • 重复下面的操作,直到擦去黑板上的所有整数:
    • 选择 l,r(l≤r) 使得数值在 [l,r] 的每个整数都至少在黑板上出现一次。然后,擦除所有数值在 [l,r] 之间的整数。
  • 设 f(L,R) 是擦除黑板上所有整数所需的最少次数。

求:\sum_{L=1}^{N}\sum_{R=L}^{N}f(L,R)

思路:对于每个子数组 [L,R],题目中定义的操作其实就是“把子数组中所有出现过的数字分成若干个连续区间(按数值连续性,即相邻数字相连),每个区间可以一次性全部删除”。因此,子数组 [L,R]所需的最少操作数 f(L,R) 就等于子数组中“数字块(区间)”的个数。

如果我们记子数组中不同数字的个数为 dis,又记其中能合并的(即相邻数字都出现了,从而可以一次删除的)个数为 merge,那么有

f(L,R) = dis - merge

对于所有以 i 为右端点的子数组 [L, i],如果数字 a[i] 在子数组中是首次出现(即 L > pos[a[i]]), 那么该子数组的 f(L, i) 需要增加 1 次操作。新增子数组个数为 i - pos[a[i]]。

接着有两种情况

1.如果 a[i]-1 在子数组中已经出现(且最后出现位置晚于上一次 a[i] 出现的位置),那么对于部分子数组,a[i] 与 a[i]-1 可以合并,减少了操作次数。

2.如果 a[i]+1 在子数组中已经出现(且最后出现位置晚于上一次 a[i] 出现的位置),那么对于部分子数组,a[i] 与 a[i]+1 可以合并,减少了操作次数。

最后更新 a[i] 的最后出现位置为当前下标 i,将当前以 i 为右端点的所有子数组的 f(L, i) 的和累加到总答案中

// Code Start Here	
	int n;
	cin >> n;
	int ans = 0,now = 0;
	vector<int> a(3*N5);
	vector<int> pos(3*N5);
	for(int i = 1;i<=n;i++){
		cin >> a[i];
		now += i - pos[a[i]];
		if(pos[a[i]-1] >= pos[a[i]])now -= pos[a[i]-1] - pos[a[i]];
		if(pos[a[i]+1] >= pos[a[i]])now -= pos[a[i]+1] - pos[a[i]];
		pos[a[i]] = i;
		ans += now;
	}
	cout << ans << endl;
	return 0;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值