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


    图的表示、图的遍历

    [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
    ");
    		}else{
    			printf("false
    ");
    		}
    	}	
    }
    -------------------------------------------

    [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("
    ");
    		}
    		printf("
    ");
    }
    void printPath(point p){
    	
    	if(p.pre==-1){
    		return;
    	}
    	p=queue[p.pre];
    	printPath(p);
    	printf("(%d,%d)
    ",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
    ",sum);
    		
    		//printPath(tmp);
    		
    	}else{
    		printf("No Path !
    ");
    	}
    	
    }
    
    
    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
    ",sum);
    	}else{
    		printf("%d
    ",-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
    ",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
    ",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
    ",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 层的新枝,
        哦,我已是枝繁叶茂。
  • 相关阅读:
    从内存池到连接池 老码农眼中的资源池
    资源池(从内存池到连接池)
    资源池设计模式 (Resource Pool)和数据池的简单实现
    数据库连接池的工作原理
    原理 : 线程池、连接池、内存池
    聚簇索引与非聚簇索引(也叫二级索引)
    MyISAM 和 InnoDB 索引的区别
    MySQL 聚簇索引&&二级索引&&辅助索引
    关于如何提高Web服务端并发效率的异步编程技术
    为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?
  • 原文地址:https://www.cnblogs.com/sctb/p/11919658.html
Copyright © 2020-2023  润新知