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(再说吧)
...

浙公网安备 33010602011771号