第八次训练课总结
huangziruidoudou · · 个人记录
P1696 [USACO18JAN] Blocked Billboard II B
这是一个二维平面上的矩形覆盖问题,我们可以使用网格标记法来计算需要覆盖的最小矩形面积。
我们先看数据范围,坐标是从-1000到+1000,那么便于存储我们可以把每一个坐标都+1000,这样就可以很好的存储和计算了,但是要注意,我们的数据范围就要扩大到2000了。
我们可以首先将割草机广告牌覆盖的网格标记为 1(表示需要被覆盖)。 然后将牛饲料广告牌覆盖的网格重新标记为 0(表示这些部分已被遮挡,不需要覆盖)。
标记以后我们就找到剩下的割草机广告牌的最大/最小的y坐标,以及最大/最小的x坐标,然后
(mx - nx + 1) * (my - ny + 1)
就求出了这块防水布的面积了。
别急,我知道你还不理解为什么是这样,那我们看个图就知道了 我们的长就是最大的x坐标-最小的x坐标,宽就是最大的y坐标-最小的y坐标,而题目里是按格点来算的长度,而我们算的是坐标之间的长度,将一个点转化成一个格点我们发现坐标x从1到3中间只有2个格子,因此我们要+1才能算出正确长度。
那么代码
#include<bits/stdc++.h>
using namespace std;
bool vis[2010][2010];
int main(){
int a, b, c, d;
cin >> a >> b >> c >> d;
a += 1000, b += 1000, c += 1000, d += 1000;//方便计算
for(int i = a; i < c; i++){
for(int j = b; j < d; j++){
vis[i][j] = 1;
}
}
int e, f, g, h;
cin >> e >> f >> g >> h;
e += 1000, f += 1000, g += 1000, h += 1000;
for(int i = e; i < g; i++){
for(int j = f; j < h; j++){
vis[i][j] = 0;
}
}
int mx = -1e9, my = -1e9, nx = 1e9, ny = 1e9;
for(int i = 0; i <= 2000; i++){
for(int j = 0; j <= 2000; j++){
if(vis[i][j]){
mx = max(mx, i);
my = max(my, j);
nx = min(nx, i);
ny = min(ny, j);
}
}
}
if(mx != -1e9)
cout << abs(mx - nx + 1) * abs(my - ny + 1);//计算面积
else//如果我全都被覆盖了
cout << 0;
}
P6207 [USACO06OCT] Cows on Skates G
这道题其实就是一个简单的四联通bfs,唯一的难点在于如何储存路径,但仔细一想,如果我走出这一步,并且这一步可以走,那我的上一步不就确定是这个一整个路径的一份子了吗?然后我把上一步的x和y坐标记录一下就可以了。
#include<bits/stdc++.h>
using namespace std;
char a[120][120];
int r, c;
struct node{
int x, y;
};
int idx = 0;
int vis[120][120];
int d[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
node pre[120][120];
node ans[120 * 120];
void bfs(){
queue<node>q;
q.push({1, 1});
for(int i = 1; i <= r; i++){
for(int j = 1; j <= c; j++){
vis[i][j] = -1;//初始化
}
}
vis[1][1] = false;
while(q.size()){
node t = q.front();
q.pop();
if(t.x == r && t.y == c){
return;
}
for(int i = 0; i < 4; i++){
int dx = t.x + d[i][0], dy = t.y + d[i][1];
if(dx < 1 || dx > r || dy < 1 || dy > c){//判断越界
continue;
}
if(vis[dx][dy] != -1){//判断走没走过
continue;
}
if(a[dx][dy] == '*'){//判断是否是障碍
continue;
}
q.push({dx, dy});
vis[dx][dy] = vis[t.x][t.y] + 1;
pre[dx][dy] = t;//记录上一步
}
}
}
int main(){
cin >> r >> c;
for(int i = 1; i <= r; i++){
for(int j = 1; j <= c;j++){
cin >> a[i][j];
}
}
bfs();
int x = r, y = c;
//反推路径
while(x != 0 && y != 0){
ans[++idx] = {x, y};
int nx = pre[x][y].x, ny = pre[x][y].y;
x = nx, y = ny;
}
//反着输出
for(int i = idx; i >= 1; i--){
cout << ans[i].x << " " << ans[i].y << endl;
}
return 0;
}
P8160 [JOI 2022 Final] 星际蛋糕 / Intercastellar
我们首先读取蛋糕初始分段信息,对于每个长度,不断除以 2 直到变为奇数,记录最终奇数长度和该长度对应的段数(初始段数为 1,每次除以 2 时段数翻倍)。为什么可以这么做呢?因为我们知道,一个数一直/2,最终的出的偶数个奇数一定都一样,所以我们直接放在一起即可,然后要记录这个奇数总共有多少个,方便后面我们查找第Xj段的长度为多少。
然后计算前缀和数组,用于快速定位查询位置所在的段。对于每个查询,使用二分查找在前缀和数组中找到第一个大于等于查询位置的索引,该索引对应的奇数长度即为所求答案。
如果你不理解,那么看了代码肯定就理解了
#include<bits/stdc++.h>
using namespace std;
struct node{
long long x, cnt;
};
node a[201000];
long long sum[2000010];
int main(){
long long n, k;
cin >> n;
for(int i = 1; i <= n; i++){
int x;
cin >> x;
int cnt = 1;
while(x % 2 == 0){//一直/2知道分成了cnt个这样的奇数
x /= 2;
cnt *= 2;//cnt计数
}
a[i].x = x, a[i].cnt = cnt;
}
//计算前缀和
sum[1] = a[1].cnt;
for(int i = 2; i <= n; i++){
sum[i] = sum[i - 1] + a[i].cnt;
}
//多组访问
int q;
cin >> q;
while(q--){
cin >> k;
/*前缀和数组 sum 的含义
假设初始蛋糕被分成 n 段,每段长度经过处理后变为奇数 a[i].x,且该段会被切分为 a[i].cnt 个最终段。sum 数组记录这些段数的前缀和:
sum[i] 表示前 i 个初始段处理后的总段数。
例如,sum[3] = a[1].cnt + a[2].cnt + a[3].cnt。
查询位置 k 的定位
当查询第 k 段的长度时,需要找到 k 落在哪个初始段处理后的区间内。然后输出这个区间的奇数*/
int pos = lower_bound(sum + 1, sum + n + 1, k) - sum;
cout << a[pos].x << endl;
}
return 0;
}