蓝桥杯之图论、广度优先、深度优先
图的表示、图的遍历
[7.1 深度优先]
求连通性
给定一个方阵,定义连通:上下左右相邻,并且值相同。
可以想象成一张地图,不同的区域被涂以不同颜色。
输入:
整数N, (N<50)表示矩阵的行列数
接下来N行,每行N个字符,代表方阵中的元素
接下来一个整数M,(M<1000)表示询问数
接下来M行,每行代表一个询问,
格式为4个整数,y1,x1,y2,x2,
表示(第y1行,第x1列) 与 (第y2行,第x2列) 是否连通。
连通输出true,否则false
例如:
10
0010000000
0011100000
0000111110
0001100010
1111010010
0000010010
0000010011
0111111000
0000010000
0000000000
3
0 0 9 9
0 2 6 8
4 4 4 6
程序应该输出:
false
true
true
思路:
深度优先,即从某一个格子出发,先选定一个方向,然后这个方向一直走下去,在每一步都重复上一部的动作,当到达最后一步时,再返回来朝其他方向走,最后走完每一步。由于时求连通性,即找到一条通路,就可以不再继续往下递归了,使用深度优先很实用。
代码:
[7.2 广度优先]
迷宫问题、最短路径
"1"是墙壁,"."是通路
求最短需要走多少步?
思路:
代码:
分酒问题
有4个红酒瓶子,它们的容量分别是:9升, 7升, 4升, 2升
开始的状态是 [9,0,0,0],也就是说:第一个瓶子满着,其它的都空着。
允许把酒从一个瓶子倒入另一个瓶子,但只能把一个瓶子倒满或把一个瓶子倒空,不能有中间状态。
这样的一次倒酒动作称为1次操作。
假设瓶子的容量和初始状态不变,对于给定的目标状态,至少需要多少次操作才能实现?
本题就是要求你编程实现最小操作次数的计算。
输入:最终状态(空格分隔)
输出:最小操作次数(如无法实现,则输出-1)
例如:
输入:
9 0 0 0
应该输出:
0
输入:
6 0 0 3
应该输出:
-1
输入:
7 2 0 0
应该输出:
2
思路:
代码:
[7.3 生成树]
标题:风险度量
X星系的的防卫体系包含 n 个空间站。这 n 个空间站间有 m 条通信链路,构成通信网。
两个空间站间可能直接通信,也可能通过其它空间站中转。
对于两个站点x和y (x != y), 如果能找到一个站点z,使得:
当z被破坏后,x和y不连通,则称z为关于x,y的关键站点。
显然,对于给定的两个站点,关于它们的关键点的个数越多,通信风险越大。
你的任务是:已经网络结构,求两站点之间的通信风险度,即:它们之间的关键点的个数。
输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,链路数。
空间站的编号从1到n。通信链路用其两端的站点编号表示。
接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条链路。
最后1行,两个数u,v,代表被询问通信风险度的两个站点。
输出:一个整数,如果询问的两点不连通则输出-1.
例如:
用户输入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
则程序应该输出:
2
思路:
通过深度优先的方法,找到所有x到达y的路径,将每次每个点被经过的次数记录下来,最后,寻找经过次数和路径数相同的点,就是割点。
代码:
[7.4 线段树]
一大堆线段,求有效覆盖长度
标题:油漆面积
X星球的一批考古机器人正在一片废墟上考古。
该区域的地面坚硬如石、平整如镜。
管理人员为方便,建立了标准的直角坐标系。
每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。
经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。
矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。
为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。
小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。
其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
注意,各个矩形间可能重叠。
本题的输入为若干矩形,要求输出其覆盖的总面积。
输入格式:
第一行,一个整数n,表示有多少个矩形(1<=n<10000)
接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。
(0<= x1,y1,x2,y2 <=10000)
输出格式:
一行一个整数,表示矩形覆盖的总面积。
例如,
输入:
3
1 5 10 10
3 1 20 20
2 7 15 17
程序应该输出:
340
再例如,
输入:
3
5 2 10 6
2 7 12 10
8 1 15 15
程序应该输出:
128
思路:
2. x排序,切分,每个条形累计面积
代码:
[7.5 并查集]
一个图中,因为连通性分成多个集团,快速求两个节点间的连通性
从某个点开始,dfs建立生成树...
剩下的点再建....
是否连通就是生成树根节点是否相同的问题
标题:合根植物
w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?
输入格式:
第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。
比如:5 * 4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
样例输入:
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
样例输出:
5
其合根情况参考图[1.png]
思路:
代码:
[7.6 作业]
青蛙跳杯子
X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色。
X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去。
如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙。
*WWWBBB
其中,W字母表示白色青蛙,B表示黑色青蛙,*表示空杯子。
X星的青蛙很有些癖好,它们只做3个动作之一:
1. 跳到相邻的空杯子里。
2. 隔着1只其它的青蛙(随便什么颜色)跳到空杯子里。
3. 隔着2只其它的青蛙(随便什么颜色)跳到空杯子里。
对于上图的局面,只要1步,就可跳成下图局面:
WWW*BBB
本题的任务就是已知初始局面,询问至少需要几步,才能跳成另一个目标局面。
输入为2行,2个串,表示初始局面和目标局面。
输出要求为一个整数,表示至少需要多少步的青蛙跳。
例如:
输入:
*WWBB
WWBB*
则程序应该输出:
2
再例如,
输入:
WWW*BBB
BBB*WWW
则程序应该输出:
10
我们约定,输入的串的长度不超过15
----------------------------
笨笨有话说:
我梦见自己是一棵大树,
青蛙跳跃,
我就发出新的枝条,
春风拂动那第 5 层的新枝,
哦,我已是枝繁叶茂。
[7.1 深度优先]
求连通性
给定一个方阵,定义连通:上下左右相邻,并且值相同。
可以想象成一张地图,不同的区域被涂以不同颜色。
输入:
整数N, (N<50)表示矩阵的行列数
接下来N行,每行N个字符,代表方阵中的元素
接下来一个整数M,(M<1000)表示询问数
接下来M行,每行代表一个询问,
格式为4个整数,y1,x1,y2,x2,
表示(第y1行,第x1列) 与 (第y2行,第x2列) 是否连通。
连通输出true,否则false
例如:
10
0010000000
0011100000
0000111110
0001100010
1111010010
0000010010
0000010011
0111111000
0000010000
0000000000
3
0 0 9 9
0 2 6 8
4 4 4 6
程序应该输出:
false
true
true
思路:
深度优先,即从某一个格子出发,先选定一个方向,然后这个方向一直走下去,在每一步都重复上一部的动作,当到达最后一步时,再返回来朝其他方向走,最后走完每一步。由于时求连通性,即找到一条通路,就可以不再继续往下递归了,使用深度优先很实用。
代码:
#include<cstdio>
int dfs(int a[50][50],int n,int y1,int x1,int y2,int x2){
if(x1==x2&&y1==y2) return 1;
int old=a[y1][x1];
a[y1][x1]=-1;
int flag=0;
if(y1>0&&a[y1-1][x1]==old&&dfs(a,n,y1-1,x1,y2,x2)) flag=1;
if(y1<n-1&&a[y1+1][x1]==old&&dfs(a,n,y1+1,x1,y2,x2)) flag=1;
if(x1>0&&a[y1][x1-1]==old&&dfs(a,n,y1,x1-1,y2,x2)) flag=1;
if(x1<n-1&&a[y1][x1+1]==old&&dfs(a,n,y1,x1+1,y2,x2)) flag=1;
a[y1][x1]=old;
if(flag) return 1;else return 0;
}
int main(){
freopen("data.in","r",stdin);
int a[50][50]={0};
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
char t[50];
scanf("%s",&t);
for(int j=0;j<n;j++){
a[i][j]=t[j]-'0';
}
}
int k;
scanf("%d",&k);
while(k--){
int y1,x1,y2,x2;
scanf("%d%d%d%d",&y1,&x1,&y2,&x2);
if(dfs(a,n,y1,x1,y2,x2)){
printf("true\n");
}else{
printf("false\n");
}
}
}
-------------------------------------------[7.2 广度优先]
迷宫问题、最短路径
...11111111111111111111111111111
11.111111........1111111111.1111
11.111111..111.11111111.....1111
11.11111111111.1111111111.111111
11.111111.................111111
11.111111.11111111111.11111.1111
11.111111.11111111111.11111..111
11..........111111111.11111.1111
11111.111111111111111.11....1111
11111.111111111111111.11.11.1111
11111.111111111111111.11.11.1111
111...111111111111111.11.11.1111
111.11111111111111111....11.1111
111.11111111111111111111111.1111
111.1111.111111111111111......11
111.1111.......111111111.1111.11
111.1111.11111.111111111.1111.11
111......11111.111111111.1111111
11111111111111.111111111.111...1
11111111111111...............1.1
111111111111111111111111111111..
如上图的迷宫,入口,出口分别:左上角,右下角"1"是墙壁,"."是通路
求最短需要走多少步?
思路:
代码:
#include<cstdio>
typedef struct mypoint{
int x,y,pre;
}point;
int front=0,tail=0;
point queue[1024];
char a[21][32];
void enqueue(point p){
queue[tail++]=p;
}
point dequeue(){
return queue[front++];
}
int isempty(){
return tail==front;
}
void visit(int x,int y){
point visit_point = {x,y,front-1};
a[x][y]='*';
enqueue(visit_point);
}
void print(int xlen,int ylen){
for(int i=0;i<=xlen;i++){
for(int j=0;j<=ylen;j++){
printf("%c",a[i][j]);
}
printf("\n");
}
printf("\n");
}
void printPath(point p){
if(p.pre==-1){
return;
}
p=queue[p.pre];
printPath(p);
printf("(%d,%d)\n",p.x,p.y);
}
int bfs(int startx,int starty,int xlen,int ylen){
point start={startx,starty,-1};
visit(start.x,start.y);
while(!isempty()){
start=dequeue();
if(start.x==xlen&&start.y==ylen){
break;
}
if(start.y+1<=ylen&&a[start.x][start.y+1]=='.'){ //right
visit(start.x,start.y+1);
}
if(start.x+1<=xlen&&a[start.x+1][start.y]=='.'){ //bottom
visit(start.x+1,start.y);
}
if(start.x-1>=0&&a[start.x-1][start.y]=='.'){ //top
visit(start.x-1,start.y);
}
if(start.y-1>=0&&a[start.x][start.y-1]=='.'){ //left
visit(start.x,start.y-1);
}
}
if(start.x==xlen&&start.y==ylen){
int sum=1;
point tmp = {start.x,start.y,start.pre};
while(start.pre!=-1){
start=queue[start.pre];
sum++;
}
printf("%d\n",sum);
//printPath(tmp);
}else{
printf("No Path !\n");
}
}
int main(){
freopen("data.in","r",stdin);
for(int i=0;i<21;i++){
char t[33];
scanf("%s",&t);
for(int j=0;j<32;j++){
a[i][j]=t[j];
}
}
print(20,31);
bfs(0,0,20,31);
return 0;
}
-------------------------------------------分酒问题
有4个红酒瓶子,它们的容量分别是:9升, 7升, 4升, 2升
开始的状态是 [9,0,0,0],也就是说:第一个瓶子满着,其它的都空着。
允许把酒从一个瓶子倒入另一个瓶子,但只能把一个瓶子倒满或把一个瓶子倒空,不能有中间状态。
这样的一次倒酒动作称为1次操作。
假设瓶子的容量和初始状态不变,对于给定的目标状态,至少需要多少次操作才能实现?
本题就是要求你编程实现最小操作次数的计算。
输入:最终状态(空格分隔)
输出:最小操作次数(如无法实现,则输出-1)
例如:
输入:
9 0 0 0
应该输出:
0
输入:
6 0 0 3
应该输出:
-1
输入:
7 2 0 0
应该输出:
2
思路:
代码:
#include<cstdio>
typedef struct {
int v[4]={0};
int pre;
}node;
node q[1000];
int head=0,tail=0;
int full[4]={9,7,4,2};
int vis[10][8][5][3]={0};
void enqueue(node n){
q[tail++]=n;
}
node dequeue(){
return q[head++];
}
void pour(node n,int x,int y){
if(n.v[x]==n.v[y]) return;
if(n.v[x]==0) return;
if(n.v[y]==full[y]) return;
if(n.v[x]+n.v[y]<=full[y]){
n.v[y]=n.v[x]+n.v[y];
n.v[x]=0;
}else{
n.v[x]=n.v[x]-(full[y]-n.v[y]);
n.v[y]=full[y];
}
n.pre=head-1;
if(vis[n.v[0]][n.v[1]][n.v[2]][n.v[3]]){
}else{
vis[n.v[0]][n.v[1]][n.v[2]][n.v[3]]=1;
enqueue(n);
}
return;
}
int equal(int a[4],int b[4]){
int flag=1;
for(int i=0;i<4;i++){
if(a[i]!=b[i]){
flag=0;
}
}
return flag;
}
int empty(){
return head==tail;
}
int bfs(int target[4]){
node start_node;
start_node.v[0]=9;
start_node.v[1]=0;
start_node.v[2]=0;
start_node.v[3]=0;
start_node.pre=-1;
vis[9][0][0][0]=1;
enqueue(start_node);
node tmp;
while(!empty()){
tmp=dequeue();
if(equal(tmp.v,target)){
break;
}
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
pour(tmp,i,j);
}
}
}
if(equal(tmp.v,target)){
int sum=0;
while(tmp.pre!=-1){
tmp=q[tmp.pre];
sum++;
}
printf("%d\n",sum);
}else{
printf("%d\n",-1);
}
}
int main(){
int a[4]={0};
scanf("%d%d%d%d",&a[0],&a[1],&a[2],&a[3]);
bfs(a);
return 0;
}
-------------------------------------------[7.3 生成树]
标题:风险度量
X星系的的防卫体系包含 n 个空间站。这 n 个空间站间有 m 条通信链路,构成通信网。
两个空间站间可能直接通信,也可能通过其它空间站中转。
对于两个站点x和y (x != y), 如果能找到一个站点z,使得:
当z被破坏后,x和y不连通,则称z为关于x,y的关键站点。
显然,对于给定的两个站点,关于它们的关键点的个数越多,通信风险越大。
你的任务是:已经网络结构,求两站点之间的通信风险度,即:它们之间的关键点的个数。
输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,链路数。
空间站的编号从1到n。通信链路用其两端的站点编号表示。
接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条链路。
最后1行,两个数u,v,代表被询问通信风险度的两个站点。
输出:一个整数,如果询问的两点不连通则输出-1.
例如:
用户输入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
则程序应该输出:
2
思路:
通过深度优先的方法,找到所有x到达y的路径,将每次每个点被经过的次数记录下来,最后,寻找经过次数和路径数相同的点,就是割点。
代码:
#include<cstdio>
int a[10001][10001]={0};
int vis[10001]={0};
long count[10001]={0};
int routs=0,m;
int x,y;
void dfs(int x){
if(x==y){
routs++;
for(int i=1;i<=m;i++){
if(vis[i]){
count[i]++;
}
}
return;
}
for(int i=1;i<=m;i++){
if(a[x][i]&&!vis[i]){
vis[i]=1;
dfs(i);
vis[i]=0;
}
}
}
int countSp(){
int c=0;
for(int i=1;i<=m;i++){
if(count[i]==routs&&i!=x&&i!=y){
c++;
}
}
return c;
}
int main() {
freopen("data.in","r",stdin);
int n;
scanf("%d%d",&m,&n);
for(int i=0;i<n;i++){
int p,q;
scanf("%d%d",&p,&q);
a[p][q]=1;
a[q][p]=1;
}
scanf("%d%d",&x,&y);
dfs(x);
int r=countSp();
if(routs==0){
printf("%d",-1);
}else{
printf("%d\n",r);
}
}
-------------------------------------------[7.4 线段树]
一大堆线段,求有效覆盖长度
标题:油漆面积
X星球的一批考古机器人正在一片废墟上考古。
该区域的地面坚硬如石、平整如镜。
管理人员为方便,建立了标准的直角坐标系。
每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。
经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。
矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。
为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。
小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。
其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
注意,各个矩形间可能重叠。
本题的输入为若干矩形,要求输出其覆盖的总面积。
输入格式:
第一行,一个整数n,表示有多少个矩形(1<=n<10000)
接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。
(0<= x1,y1,x2,y2 <=10000)
输出格式:
一行一个整数,表示矩形覆盖的总面积。
例如,
输入:
3
1 5 10 10
3 1 20 20
2 7 15 17
程序应该输出:
340
再例如,
输入:
3
5 2 10 6
2 7 12 10
8 1 15 15
程序应该输出:
128
思路:
2. x排序,切分,每个条形累计面积
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
typedef struct myseg{
int l,r,h,d;
myseg(){}
myseg(int l,int r,int h,int d):l(l),r(r),h(h),d(d){}
bool operator<(const myseg &s){
return h<s.h;
}
}seg;
typedef struct mynode{
int cnt;
int len;
}node;
seg a[20002];
int all[10001];
node t[20002];
void pushdown(int l,int r,int rt){
if(t[rt].cnt){
t[rt].len=all[r+1]-all[l];
}else if(l==r){
t[rt].len=0;
}else{
t[rt].len=t[2*rt].len+t[2*rt+1].len;
}
}
void update(int L,int R,int l,int r,int rt,int val){
if(L<=l&&r<=R){
t[rt].cnt+=val;
pushdown(l,r,rt);
return;
}
int m=(l+r)/2;
if(L<=m) update(L,R,1,m,rt*2,val);
if(R>m) update(L,R,m+1,r,rt*2+1,val);
pushdown(l,r,rt);
}
int main(){
freopen("data.in","r",stdin);
int n,k=0;
scanf("%d",&n);
for(int i=0;i<n;i++){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
a[k]=seg(x1,x2,y1,1);
all[k++]=x1;
a[k]=seg(x1,x2,y2,-1);
all[k++]=x2;
}
sort(all,all+k);
sort(a,a+k);
int m=unique(all,all+k)-all;
int ans=0;
for(int i=0;i<m;i++){
int l=lower_bound(all,all+m,a[i].l)-all;
int r=lower_bound(all,all+m,a[i].r)-all-l;
update(l,r,0,m,1,a[i].d);
ans+=t[1].len*(a[i+1].h-a[i].h);
}
printf("%d\n",ans);
}
-------------------------------------------[7.5 并查集]
一个图中,因为连通性分成多个集团,快速求两个节点间的连通性
从某个点开始,dfs建立生成树...
剩下的点再建....
是否连通就是生成树根节点是否相同的问题
标题:合根植物
w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?
输入格式:
第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。
比如:5 * 4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
样例输入:
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
样例输出:
5
其合根情况参考图[1.png]
思路:
代码:
#include<cstdio>
int a[1000*1000+1]={0};
int count=0;
int find(int n){
if(a[n]==0){
return n;
}
return a[n]=find(a[n]); //并査集核心代码
}
void g(int x,int y){
int p,q;
if((p=find(x))!=(q=find(y))){
a[p]=q;
count++;
}
return;
}
int main(){
int m,n;
scanf("%d%d",&m,&n);
int k;
scanf("%d",&k);
for(int i=0;i<k;i++){
int x,y;
scanf("%d%d",&x,&y);
g(x,y);
}
printf("%d\n",m*n-count);
}
------------------------------------------[7.6 作业]
青蛙跳杯子
X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色。
X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去。
如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙。
*WWWBBB
其中,W字母表示白色青蛙,B表示黑色青蛙,*表示空杯子。
X星的青蛙很有些癖好,它们只做3个动作之一:
1. 跳到相邻的空杯子里。
2. 隔着1只其它的青蛙(随便什么颜色)跳到空杯子里。
3. 隔着2只其它的青蛙(随便什么颜色)跳到空杯子里。
对于上图的局面,只要1步,就可跳成下图局面:
WWW*BBB
本题的任务就是已知初始局面,询问至少需要几步,才能跳成另一个目标局面。
输入为2行,2个串,表示初始局面和目标局面。
输出要求为一个整数,表示至少需要多少步的青蛙跳。
例如:
输入:
*WWBB
WWBB*
则程序应该输出:
2
再例如,
输入:
WWW*BBB
BBB*WWW
则程序应该输出:
10
我们约定,输入的串的长度不超过15
----------------------------
笨笨有话说:
我梦见自己是一棵大树,
青蛙跳跃,
我就发出新的枝条,
春风拂动那第 5 层的新枝,
哦,我已是枝繁叶茂。