AtCoder Beginner Contest 355A~E

AtCoder Beginner Contest 355A~E

A - Who Ate the Cake?(模拟)

题意&思路

给的A和B如果不相等,那就是第三个人。否则推不出来

代码

#include <bits/stdc++.h>
using namespace std;
void solve() {
	int a, b; cin >> a >> b;
	vector<int>v(4);
	v[a] = 1;
	v[b] = 1;
	if (a != b) {
		for (int i = 1; i <= 3; i++) {
			if (!v[i]) {
				cout << i;
			}
		}
	}
	else cout << -1;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int t; t = 1;
	while (t--) {
		solve();
	}
	return 0;
}

B - Piano 2(模拟)

题意

给数组a和数组b,两个数组合在一起再排序得到数组c,每一个数字都不一样。问数组c中是否存在两个相邻的数字都是数组a的数字(被翻译卡了很久,太唐了)

思路

把数组a中的数字都标记一下,然后遍历数组c,逐个判断当前数字和下一个数字是否同时被标记

代码

#include <bits/stdc++.h>
using namespace std;
void solve() {
	int n, m; cin >> n >> m;
	vector<int>a(n), b(m),c;
	vector<int>vis(205);
	for (int i = 0; i < n; i++) {
		cin >> a[i];
		vis[a[i]] = 1;
		c.push_back(a[i]);
	}
	for (int i = 0; i < m; i++) {
		cin >> b[i];
		c.push_back(b[i]);
	}
	sort(c.begin(), c.end());
	bool flag = false;
	for (int i = 0; i < n + m -1; i++) {
		if (vis[c[i]] && vis[c[i + 1]]) {
			flag = true;
			break;
		}
	}
	if (flag) cout << "Yes";
	else cout << "No";
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t; t = 1;
	while (t--) {
		solve();
	}
	return 0;
}

C - Bingo 2(模拟)

题意

给N*N的数组,1~N*N依次填入数组。然后给一些数字,按顺序每次点亮一个数字,问最早什么时候Bingo

思路

如果是朴素做法,直接就每次点亮一个数字,然后去遍历整个数组就行,但是肯定就会超时。

  • 如何在每次点亮一个数字之后快速判断是否Bingo?

    怎么才算Bingo呢,其实可以转换成每一行/每一列/每一个对角线上,被点亮的数字的数量变为N时,就Bingo

    那就给每一行、每一列、两个对角线各自一个变量,代表了某一行/列/对角线上被点亮的数字的数量

  • 给定一个数字num,如何判断这个数字属于哪一行/列/对角线?

    (num / n) + 1 是行

    (num % n) 是列

    if (num % n == 0) {

    l = n;

    r--;

    }

    左上到右下对角线:行 == 列

    左下到右上对角线:行 + 列 == n + 1

代码

#include <bits/stdc++.h>
using namespace std;
void solve() {
	int n, T; cin >> n >> T;
	vector<int>row(n + 1), line(n + 1);
	int spe1 = 0, spe2 = 0;
	int num, r, l;
	for (int i = 1; i <= T; i++) {
		cin >> num;
		r = num / n + 1;
		l = num % n;
		if (num % n == 0) {
			l = n;
			r--;
		}
		row[r]++;
		line[l]++;
		if (r == l)spe1++;
		if (r + l == n + 1)spe2++;
		if (row[r] == n || line[l] == n || spe1 == n || spe2 == n) {
			cout << i;
			return;
		}
	}
	cout << -1;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t; t = 1;
	while (t--) {
		solve();
	}
	return 0;
}
/*
	搞2* n个数组,其中n个对应1~n行,另外n个对应1~n列,
	如果某个数字属于某行/某列,把他加入到对应的行/列,每次判断这个数组的数量是否到达n,线性时间复杂度

	给定一个数字num,怎么判断它属于哪一行/列?
	(num / n) + 1 是行
	(num % n) 是列

	if (num % n == 0) {
		l = n;
		r--;
	}

	左上到右下对角线:行 == 列
	左下到右上对角线:行 + 列 == n + 1
*/

D - Intersecting Intervals(双指针)

题意

给一堆n个区间,然后问有多少对区间相交

思路

一开始尝试直接去算相交区间的数量,但是想不到怎么算,好难。其实可以转换成求没有相交的区间对数,没有相交说明靠前面的区间的右端点r1,在靠后面的区间的左端点l2前面

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int l[N], r[N];

void solve() {
	long long n; cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> l[i] >> r[i];
	}
	sort(l+1, l + 1+n);
	sort(r+1, r + 1+n);
	long long res = n * (n - 1) / 2;
	// 枚举右区间
	for (int i = 1, j = 1; i <= n; i++) {
		while ( r[j] < l[i]) {
			j++;
		}
		res -= j-1;
	}
	cout << res;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; //cin >> _;
	_ = 1;
	while (_--) {
		solve();
	}
	return 0;
}

E - Guess the Sum(图论,最短路)

超级无敌旋转陀螺图论题,使我的大脑旋转

题意

  • 互动题,系统有一个长度为2N的数组,然后给左右端点L,R,求ALAR-1~的和
  • 每次可以问一对i,j。但是要满足l = 2i*j 和 r = 2i(j+1)−1,然后会给Al到Ar的和(包含端点)

思路

俺也是抄的别人的代码,真的看了老半天,第一次写这样的题

  • 这题可以把端点转换成图上的点,然后区间段(块)就变成了边。(这里的区间块指的是按照题目意思,可以枚举的出来的所有区间)

    区间设置成左闭右开,比如l->r+1就可以得到[l,r+1)的和,也就是[l,r]

  • 边的方向也有讲究,如果是较小点指向较大点,那么这条边的贡献就是正的,如果是较大点,那么这条边的贡献就是负的

    比如要求A1到A8的和,也就是[1,8],那就可以是1->0->9(先不用管图上是否真的有这样的点/边,只是想展示边的方向代表的意思)

    首先第一段1->0,是大端点指向小端点,那就让它反回来,变成0->1,代表了[0,1)的和,但是由于一开始大->小,所以这条边的贡献是负的。

    然后第二段0->9,是小->大,不用反过来,同时贡献是正的,代表了[0,9)的和

    两段加起来 +[0,9) -[0,1) 变成[0,8] - [0,0],那就是[1,8]

  • 然后题目要l到r的和,那就找到 l->r+1 的路径就行(具体看代码吧,为了理解这个题,我代码看了很久o.O,很多注释)

代码

#include <bits/stdc++.h>
#include <array>
using namespace std;

void solve() {
	int n, l, r; cin >> n >> l >> r;
	int len = 1 << n;
	vector<vector<int>>edge(len + 1);	// 下标要从0~N-1,多一个N可以作为左闭右开的右端点

	// 建图:枚举区间段长度->枚举起点->找到右端点->加边
	for (int i = 0; (1 << i) <= len; i++) {			// 区间段长度是2^i
		for (int s = 0; s < len; s += (1 << i)) {	// s = 2^i *j(实质是枚举j,就相当于倍增了2^i)

			int e = s + (1 << i);					// 左闭右开
			/*	 
				这里e不会超过len,最多==len
				假设区间段长度(1<<i)就是等于len了,
				那显然s==0就是唯一一个起点,因为s的下一个值会是s+=(1<<i)等于len,取不到
				所以这里看s为0,长度为len的时候,e取到了len,不会超过len
			*/

			edge[s].push_back(e);					// 从小端点到大端点
			edge[e].push_back(s);					// 大端点到小端点
		}
	}

	// 接下来通过BFS找到l到r的最短路,同时要记录每一个点的前驱(最后要通过路径来计算答案)
	vector<array<int, 2>>road;		// 这个数组用来记录路径
	vector<int>pre(len + 1,-1);		// 这里先初始化为-1,如果是-1的话,那就说明这个点没有前驱

	// 先把l的前驱设置成0,后面得到最终路径的时候,
	// 如果路径不包含0的话,是不会把l的前驱0算进去的(具体看一下得到最终路径的逻辑)
	pre[l] = 0;
	queue<int>q;
	q.push(l);
	while (!q.empty()) {
		int cur = q.front();
		q.pop();
		if (cur == r + 1) {			// 注意我们建图是左闭右开,要让路径包含r的话,那就要走到r+1
			break;					// 同时我们是按层搜索的,第一次找到r+1,那就是最短路径了
		}
		for (int nex : edge[cur]) {	// 遍历cur连接的所有点,推入队列(BFS,按层搜索了,所以路径一定是最短的)
			if (pre[nex] == -1) {	// 前驱是-1,所以nex还没有被搜索到过,可以推入队列
				pre[nex] = cur;		// 同时标记nex的前驱是cur(nex是从pre那里来的)
				q.push(nex);
			}
		}
	}

	// 此时已经找到r+1了,只要从r+1开始,不断找每一个点的前驱,直到找到l,那就找到了完整路径
	// 因为我们只能记录每个点的前驱pre,所以是从终点开始往前推,找起点
	// 终止条件是cur为l,所以不会走到pre[l]=0,同时如果l是0的话,那l自然已经在路径里面了,也没问题
	for (int cur = r + 1; cur != l; cur = pre[cur]) {	
		road.push_back({ pre[cur], cur });
	}
	// 这时候我们的road从road[0]~road[...],
	// 第一个路径是	pre[r+1],r+1,也就是	pre[r+1]->r+1
	// 第二个路径就是						pre[pre[r+1]]->pre[r+1]
	// 路径的排序是反过来了,但是路径没有错,可以翻转一下road,让road从l开始
	//(当然也可以不翻转,因为计算答案的时候只在乎每一个路径指的方向,只在乎这个区间段的贡献是正还是负)
	reverse(road.begin(), road.end());	// (可以不要)

	long long sum = 0;
	for (auto& [u, v] : road) {
		int sign = 1;					// 如果是从小端点到大端点,那贡献就是正的,反之是负的
		if (u > v) {
			sign *= -1;
			swap(u, v);
		}

		// 知道了区间的左右端点u,v,就要问题目这个区间段的和了。但是提问是问i和j,所以还要计算一下i,j
		int i = log2(v - u);			// 2^i是区间长度,所以i是log2(区间长)(v-u是区间长度)
		int j = u >> i;					// (2^i)*j = u -> j = u/(2^i) -> j = u*(2^-i) 所以是u右移i位
		cout << "? " << i << " " << j << endl;
		int w; cin >> w;
		sum += w * sign;				// 权值乘上正/负贡献
	}

	sum = (sum % 100 + 100) % 100;		// 少了就炸了
	cout << "! " << sum;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	while (_--) {
		solve();
	}
	return 0;
}
/*
	 以每个起点作为块的起点,枚举区间块长度,把边看作区间块,图中点作为区间端点(左闭右开)

	 通过BFS找到从l到r的路径

	 路径上每条边提供贡献

	 区间左到右:贡献为正
	 区间右到左:贡献为负
*/

F - MST Query(再说吧)

...

posted @ 2025-04-17 21:08  zombieee  阅读(19)  评论(0)    收藏  举报