蓝桥杯之图论、广度优先、深度优先

图的表示、图的遍历

[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 层的新枝,
    哦,我已是枝繁叶茂。
posted @ 2018-04-18 15:28  abin在路上  阅读(234)  评论(0编辑  收藏  举报