题目列表
AcWing 4419. 上车
题目描述
有 n 辆客车,其中第 i 辆车的已载客数为 pi,最大载客数为 qi。
现在,来了两个人想要乘坐同一辆车前去旅行。
请问,一共有多少辆客车可供他们选择。
输入格式 第一行包含整数 n。
接下来 n 行,每行包含两个整数 pi,qi。
输出格式 一个整数,表示可供选择的客车数量。
数据范围 前三个测试点满足 1≤n≤3。 所有测试点满足 1≤n≤100,0≤pi≤qi≤100。
输入样例1: 3 1 1 2 2 3 3 输出样例1: 0 输入样例2: 3 1 10 0 10 10 10 输出样例2: 2
分析
签到题,遍历一下所有客车的最大载客量q和已载客量p,统计下q - p >= 2的客车的数量即可。
代码
#include <iostream>
using namespace std;
int main(){
int p,q,n;
cin>>n;
int res = 0;
for(int i = 0;i < n;i++) {
cin>>p>>q;
if(q - p >= 2) res++;
}
cout<<res<<endl;
return 0;
}
AcWing 4420. 连通分量
题目描述
给定一个 n×m 的方格矩阵,每个方格要么是空格(用 . 表示),要么是障碍物(用 * 表示)。
如果两个空格存在公共边,则两空格视为相邻。
我们称一个不可扩展的空格集合为连通分量,如果集合中的任意两个空格都能通过相邻空格的路径连接。
这其实是一个典型的众所周知的关于连通分量(Connected Component )的定义。
现在,我们的问题如下:
对于每个包含障碍物的单元格 (x,y),假设它是一个空格(所有其他单元格保持不变)的前提下,请你计算包含 (x,y) 的连通分量所包含的单元格数量。
注意,所有假设求解操作之间都是相互独立的,互不影响。
输入格式 第一行包含两个整数 n,m。
接下来 n 行,每行包含 m 个字符:. 表示空格,* 表示障碍物。
输出格式 输出一个 n 行 m 列的字符矩阵,其中第 i 行第 j 列的字符对应给定矩阵中第 i 行第 j 列的单元格。
如果该单元格为空格,则输出字符为 .,如果该单元格为障碍物,则输出字符为假设该单元格为空格的前提下,包含该单元格的连通分量所包含的单元格数量对 10 取模后的结果。
具体格式可参照输出样例。
数据范围 前 5 个测试点满足 1≤n,m≤10。 所有测试点满足 1≤n,m≤1000。
输入样例1: 3 3 . .. . 输出样例1: 3.3 .5. 3.3 输入样例2: 4 5 … …* ... ..* 输出样例2: 46…3 …732 .6.4. 5.4.3
分析
题目给了一个n * m的矩阵,里面*表示障碍,.表示空格,比如输入矩阵为*.*,则输出矩阵为2.2,.对应位置的输出不变,*对应位置则将其视为空格,并输出其所在连通块中单元格的数量。本来只是个简单的flood fill问题,我们遍历一下输入的矩阵,对每个*都进行一下bfs求出其连通块的数量即可,但是本题的矩阵规模较大,直接暴力BFS会TLE。比如有个1000*1000的矩阵,第一行全是*,后面行的元素的都是.。则一共要做1000次bfs,每次要遍历999*1000规模的矩阵,问题规模就变成了109,显然会超时。 超时的原因在于重复遍历,上面举的例子就是每次BFS都会重复遍历后面的999行矩阵,显然是没有必要的,解决办法也很简单,求出原始矩阵中每个连通块的数量,也就是矩阵中每个元素最多遍历一次,BFS完后需要记录每个.所在连通块的编号和该连通块中单元格的数量。对于任一个*而言,其最多与上下左右四个方向的.相邻,比如说其四个相邻的连通块的编号是1,2,3,4,就只需要将四个连通块的规模加上1就是将该单元格变成空格后所处连通块的规模了;由于*相邻的连通块可能重复,比如上下左右的连通块编号分别是1 1 2 2,那么就只需要加上1和2号连通块的规模即可,去重操作使用set即可。 尽管本题比一般的flood fill在连通块的统计上要稍微麻烦点,但是也是可以很快解决的。周赛时我犯了一个愚蠢的并且不太常见的错误,直到结束后写了对拍程序才发现问题。上面说到,需要对输入矩阵做次BFS统计下每个连通块的规模并且标记每个空格属于哪个连通块。统计连通块的数量很简单,使用个num数组来存储编号为idx的连通块的数量即可。给每个空格标记所属连通块的编号,也可以定义个二维数组来存储。但是出于能复用数组就不重新开的原则,我就直接用原来存储矩阵的数组来存储连通块的编号了,比如原来由’和.构成的矩阵用字符数组g来存储,在BFS的过程中,我直接使用g[i][j] = ‘0’ + idx,来对单元格进行编号,比如g[i][j]属于编号为2的连通块,g[[][j] = ‘2’。理论上看起来可行,却忽略了g的数据类型只是char,而连通块的编号最多可以达到106规模,而char只能存储到127,这样复用char数组就会爆char,平时只关心会不会爆int,到这题爆char了也是个惨痛的教训。当时WA后检查无数遍逻辑也没想到是char数组超出表示范围的问题,当然解决的办法也很简单,重新开个int类型的二维数组来存储连通块的编号就可以了。 最后,在统计连通块的规模时还有点需要注意,就是BFS在标记每个连通块的编号时需要在入队时标记,不能在出队时标记。因为入队时的一个条件就是没有被标记过说明之前没有访问过,可以入队。如果出队时才标记,假设空格x和y都与空格z相邻,那么x出队时访问z将z入队,y出队时又将z入队,这样一来由于z的重复入队,会导致连通块规模统计结果偏大,入队时标记就不会出现该问题了。
代码
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;
const int N = 1005;
char g[N][N];
int flag[N][N];
int n,m;
pair<int,int> q[N*N];
int idx,num[N*N];
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
void bfs(int x,int y) {
idx++;
int hh = 0,tt = 0;
q[0] = {x,y};
flag[x][y] = idx;
while(hh <= tt) {
auto u = q[hh++];
x = u.first,y = u.second;
for(int t = 0;t < 4;t++) {
int i = x + dx[t],j = y + dy[t];
if(i >= 0 && i < n && j >= 0 && j < m && g[i][j] == '.' && !flag[i][j]) {
q[++tt] = {i,j};
flag[i][j] = idx;
}
}
}
num[idx] = tt+1;//连通块的规模与队列规模一致
}
int solve(int x,int y) {
set<int> s;
int res = 1;
for(int t = 0;t < 4;t++) {
int i = x + dx[t],j = y + dy[t];
if(i >= 0 && i < n && j >= 0 && j < m && g[i][j] == '.') {
s.insert(flag[i][j]);
}
}
for(auto x : s) {
res += num[x];
}
return res;
}
int main(){
cin>>n>>m;
for(int i = 0;i < n;i++) cin>>g[i];
for(int i = 0;i < n;i++) {
for(int j = 0;j < m;j++){
if(g[i][j] == '.' && !flag[i][j]) bfs(i,j);
}
}
for(int i = 0;i < n;i++) {
for(int j = 0;j < m;j++) {
if(g[i][j] == '*') cout<<solve(i,j) % 10;
else cout<<".";
}
cout<<endl;
}
return 0;
}
AcWing 4421. 信号
题目描述
有 n 个房子排成一排,从左到右依次编号为 1~n。
其中一些房子内装有无线信号发射器。
这些信号发射器的有效覆盖半径为 r。
更准确地说,如果第 p 号房子内装有信号发射器,则所有房间编号在 [p?r+1,p+r?1] 范围内的房子均可被其发出的无线信号覆盖,而其余房子则不会被其发出的无线信号覆盖。
例如,假设 n=6,r=2,且第 2、5 号房子内装有信号发射器,则第 2 号房子内的发射器发出的信号可以覆盖第 1~3 号房子,第 5 号房子内的发射器发出的信号可以覆盖第 4~6 号房子,将两个发射器全部打开,则无线信号可以覆盖到所有房子。
初始时,所有无线信号发射器都是关闭的,请计算至少开启多少个无线信号发射器,才能保证所有房子都被无线信号覆盖到。
输入格式 第一行包含两个整数 n 和 r。
第二行包含 n 个整数 a1,a2,…,an,每个数要么是 1,要么是 0,ai=1 表示第 i 号房子内装有无线信号发射器,ai=0 表示第 i 号房子内未装无线信号发射器。
输出格式 一个整数,表示需要开启的无线信号发射器的最少数量。
如果无法使得所有房子都被无线信号覆盖到,则输出 ?1。
数据范围 前 6 个测试点满足 1≤n≤10。 所有测试点满足 1≤n,r≤1000,0≤ai≤1。
输入样例1: 6 2 0 1 1 0 0 1 输出样例1: 3 输入样例2: 5 3 1 0 0 0 1 输出样例2: 2 输入样例3: 5 10 0 0 0 0 0 输出样例3: -1 输入样例4: 10 3 0 0 1 1 0 1 0 0 0 1 输出样例4: 3
分析
本题考察贪心,难度不大。有1~n个房子,每个房子如果有发射器的话信号可以覆盖左边r - 1和右边r - 1个房子,求开启多少个发射器信号才能覆盖所有的房子。首先考察第一个发射器应该放在哪,其位置可达不能超过r,否则将覆盖不到左边的房子,位于r的发射器刚好可以覆盖到左边编号为1的房子。选择发射器的原则就是在能覆盖到左边盲区的同时尽可能最大的覆盖到右边的区域,编号1到r位置的发射器都能覆盖其左边的房子,但是r能覆盖到右边更多的房子,所以优先选r。也就是说,第一个发射器从第r位置往左找,找到第一个安装有发射器的房子开启该发射器,开启了第一个发射器,第二个发射器的位置也可以贪心的开启。假设第一个发射器位于q,则其可以向右覆盖到编号为q + r - 1的房子,第二个发射器最远可以安装在q + 2r - 1的位置,其最左可以覆盖到编号q + r的房子,所以第二个发射器可以从q + 2r - 1向左枚举。如果枚举到q了还没找到发射器,说明不存在合法方案了。这个方法用p去找下一个发射器的位置,q则表示上一个发射器的位置。 还可以只存储包含发射器的房子编号,省去遍历所有房子的麻烦。并且我们可以用q表示上一个发射器能够覆盖的最右的位置,p在包含发射器房子的编号集合里找下一个发射器,初始q = 0表示上一个发射器最远覆盖到编号为0的房子,属于哨兵节点。使用a[i]表示第i个发射器的位置,p从1搜到a[p] <= q + r的最右的位置,如果a[p] <= q - r + 1,说明q到q + r的位置无法覆盖到,否则下一个发射器设在a[p],同时更新q = a[p] + r - 1。
代码
方法一:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int a[N];
int main(){
int n,r;
cin>>n>>r;
int res = 0;
for(int i = 1;i <= n;i++) cin>>a[i];
int q = 0;
for(int p = r;!q || q < n - r + 1;p += 2 * r - 1) {
p = min(p,n);
while(p > q && !a[p]) p--;
if(p == q) {
res = -1;
break;
}
q = p;
res++;
}
cout<<res<<endl;
return 0;
}
方法二:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int a[N];
int main(){
int n,r,x;
cin>>n>>r;
int res = 0,cnt = 0;
for(int i = 1;i <= n;i++){
cin>>x;
if(x) a[++cnt] = i;
}
a[0] = -1000;//哨兵节点,如果r过大,找不到合法解时可以命中。
int p = 0, q = 0;
while(true) {
while(p < cnt && a[p + 1] - q <= r) p++;
if(a[p] <= q - r + 1) {
res = -1;
break;
}
q = a[p] + r - 1;
res++;
if(q >= n) break;
}
cout<<res<<endl;
return 0;
}
|