LCA(倍增+Tarjan)和BFS、DFS以及Prim、Kruskal

LCA

对于一棵树,求两个节点的最近公共祖先(LCA)。

如下图

​ 1和 6 的 LCA 是 8 。

11 和 1 的 LCA 是 8 。

11 和 15 的 LCA 是 4 。

14 和 13 的 LCA 是 1 。

在这里插入图片描述

Code

int LCA(int x, int y)
{
    if (Depth[x] < Depth[y])
        swap(x, y);							//默认x为较低节点

    for (int i = MaxDepth; i >= 0; i--) {	//从大到小枚举,保证结果最优(加的上则必须加上)
        if (Depth[Jump[x][i]] >= Depth[y])
            x = Jump[x][i];					//如果超过y则不跳,否则可以跳
        if (x == y)							//相等说明为公共祖先
            return x;
    }

    for (int i = MaxDepth; i >= 0; i--) {	//同时跳
        if (Jump[x][i] != Jump[y][i]) {		//如果不相同则加上,否则不加,正确性由上个循环可知
            x = Jump[x][i];
            y = Jump[y][i];
        }
    }

    return Jump[x][0];						//最后一定停在公共祖先的下面一层
}

倍增

??什么叫倍增

我们知道:
20 = 2 4 + 2 2 20=2^{4}+2^{2} 20=24+22
这就是一种用2的倍增求解20的等号。

字面的上意思看就是成倍的增长

倍增的思想很简单,首先进行预处理,用一个深搜将每个点的深度和它向上跳一步到达的点(也就是它的父节点)处理出来,然后用下列递推式
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] f[i][j]=f[f[i][j-1]][j-1] f[i][j]=f[f[i][j1]][j1]

求出该点跳2^j步所到达的点。这里解释一下,为什么是
f [ f [ i ] [ ] j − 1 ] [ j − 1 ] f[f[i][]j-1][j-1] f[f[i][]j1][j1]
?因为倍增每次都是跳的2的整数次幂步,而2j=2(j-1)+2^(j-1);这样就不难理解了。

并查集

什么叫并查集?

简单来说就是修路问题,现在有一个村庄,有很多规划路段,我们的要求就是修最短的路把所有的村庄连接起来,但不一定有直接的道路相连,只要相互之间可达即可,那么把原来一些零散的村落通过路连接起来那么现在就成了一个小聚居,即所有的村庄之间可以互通了,那么他们就属于一个集合了。

亲戚:

题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

初始化:

int vect[MAXN];
void init(int n)
{
for(int i=1;i<=n;i++)
	vect[i]=i;
}

一开始,我们先将它们的父节点设为自己。

查询:

int find(int x)
{
	if(vect[x]==x)
		return x;
	else
		return find(vect[x]);
}

判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可

合并:

void merge(int i,int j)
{
	vect[find(i)]=find(j);
}

先找到两个集合的代表元素,然后将前者的父节点设为后者即可

题:

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述

规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

输入格式

第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。

以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。

接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

输出格式

P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

输入输出样例

输入 #1复制

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

输出 #1复制

Yes
Yes
No

思路

1.初始化每个人的祖先就是自己

2.输入两个人,然后将两个人的祖先合并

3.输入,判断两个人的祖先是否相同(查找两个人的祖先是否相同),输出判断的结果

Code

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int f[5000];
int fun(int x)
{
	return x==f[x]?x:fun(f[x]);
}
int main()
{
	int n,m,p;
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++)f[i]=i;//初始化
	while(m--)
	{
		int x,y;
		cin>>x>>y;
		int l=fun(x);
		int r=fun(y);
		if(l!=r)f[l]=r;//判断是否相等,如果不相等,就把l的祖先设置为r,合并。	
	}
	for(int i=1;i<=p;i++)
	{
		int x,y;
		cin>>x>>y;
		if(fun(x)==fun(y))//判断测试样例
		{
			cout<<"Yes"<<endl;
		}
		else cout<<"No"<<endl;
	}
}
 

Tarjan算法

求有向图中强连通分量的算法。

预备知识:有向图,强连通。

有向图:由有向边的构成的图。需要注意的是这是Tarjan算法的前提和条件。

强连通:如果两个顶点可以相互通达,则称两个顶点 强连通(strongly connected)。如果有向图G的每两个顶点都 强连通,称G是一个强连通图。非 强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

推荐观看

Code

Tarjan(u)           //根节点u
{
    for each(u,v)
    {
        Tarjan(v);  //v还有儿子节点
        join(u,v);  //把v合并到u上
        vis[v]=1;   //访问标记
    }
    for each(u,v)   //遍历与u有询问关系的节点v
    {
        if(vis[v])
        {
            ans=find(v);
        }
    }
}

LCA和倍增

对于每两个询问的点,只需要先找出那个点的深度更深,就将它跳跃到与另一个点深度相同,如果此时两个点相同,那么这个点就是最近公共祖先;如果不相同,两个点就一起跳,直找到最近公共祖先为止。

初始化

void Init(int Point, int ItsFather)
{
    Depth[Point] = Depth[ItsFather] + 1;		//得到结点深度
    for (int i = 0; i <= MaxDepth; i++)			//MaxDepth为最大深度,一般置为20即可
        Jump[Point][i + 1] = Jump[Jump[Point][i]][i];
        										//由于从根开始遍历,所以该点的父节点一定先于该点被访问
    for (int e = First[Point]; e; e = Edges[e].next) {
        int To = Edges[e].to;					//枚举从当前点出发的每条边

        if (To == ItsFather)					//若不是父节点
            continue;
        Jump[To][0] = Point;					//则对面点跳0步回到该点
        Init(To, Point);						//递归进行初始化
    }
}

模板

void dfs(int u,int pre,int d)  //预处理出每个节点的深度及父亲节点
{
    fa[u][0]=pre;
    depth[u]=d;
    for(int i=0;i<vec[u].size();i++)
    {
        int v=vec[u][i];
        if(v!=pre)
        {
            dfs(v,u,d+1);
        }
    }
}
 
void init()                    //预处理出每个节点往上走2^k所到的节点,超过根节点记为-1
{
    dfs(root,-1,0);              //root为根节点
    for(int j=0;(1<<(j+1))<n;j++)   //n为节点数目
    for(int i=0;i<n;i++)
    {
        if(fa[i][j]<0) fa[i][j+1]=-1;
        else fa[i][j+1]=fa[fa[i][j]][j];
    }
}
 
int LCA(int u,int v)
{
    if(depth[u]>depth[v]) swap(u,v);
    int temp=depth[v]-depth[u];
    for(int i=0;(1<<i)<=temp;i++)      //使u,v在同一深度
    {
        if((1<<i)&temp)
            v=fa[v][i];
    }
    if(v==u) return u;
    for(int i=(int)log2(n*1.0);i>=0;i--)  //两个节点一起往上走
    {
        if(fa[u][i]!=fa[v][i])
        {
            u=fa[u][i];
            v=fa[v][i];
        }
    }
    return fa[u][0];
}

LCA和Tarjan

伪代码

Tarjan(u)//marge和find为并查集合并函数和查找函数
{
  for each(u,v)    //访问所有u子节点v
  {
      Tarjan(v);        //继续往下遍历
      marge(u,v);    //合并v到u上
    标记v被访问过;
  }
  for each(u,e)    //访问所有和u有询问关系的e
  {
      如果e被访问过;
      u,e的最近公共祖先为find(e);
  }
}

讲解:

深度优先搜索


bool Dfs(V) { 
if( V 为终点)
return true;
if( V 为旧点)
return false;
将V标记为旧点;
对和V相邻的每个节点U { 
if( Dfs(U) == true)
return true;
}
return false; 
} 

int main()
{
将所有点都标记为新点;
起点 = 1
终点 = 8
cout << Dfs(起点); }


如果需要记录路径的话:加上path[depth];


Node path[MAX_LEN]; //MAX_LEN取节点总数即可
int depth;
bool Dfs(V) {
if( V为终点){
path[depth] = V;
return true;
}
if( V 为旧点)
return false;
将V标记为旧点;
path[depth]=V;
++depth;
1516
对和V相邻的每个节点U { 
if( Dfs(U) == true)
return true;
}
--depth;
return false;
}

广搜BFS

#include <iostream>
#include <queue>
#include <algorithm>
#define N 100
using namespace std;
int n, m;
int  map[N][N];
bool vis[N][N];
//一般情况下的方向数组 具体的视情况列方向数组
int  dir[4][2] = { {0,1},{0,-1},{1,0},{-1,0} }; //方向数组

struct node{
    int x;
    int y;
    int step; //可以记录最小步数之类的最优解
    node(int x, int y ,int step) :x(x), y(y),step(step) { }
    node(){ }
};
//判断是否在图中
bool inmap(int x, int y)
{
    if (x >= 1 && x <= n && y >= 1 && y <= m)return true;
    else return false;
}

void bfs(int sx, int sy)
{
    vis[sx][sy] = 1; //标注起始点
    queue<node> q;
    q.push(node(sx, sy ,0));
    while (!q.empty())
    {
        node now = q.front();
        for (int i = 0; i < 4; i++)
        {
            int x = now.x + dir[i][0];
            int y = now.y + dir[i][1];
            int step = now.step + 1;  //一次只走一步
            if (inmap(x, y) == true && vis[x][y] == false)
            {
                //此处还可以进行别的剪枝
                //可以是多个条件剪枝或者终止条件由具体题目而定
                if (到达终点或者是其他剪枝操作)
                {
                    //进行最后处理其他剪枝操作....  
                    return;
                }
                else {
                    vis[x][y] = 1; //标记该点已经来过并将其放入队列
                    q.push(node(x, y,step + 1));
                }
            }
            else return;
        }
        q.pop();
    }
    return;
}
void init()
{
    int sx, sy;
    cin >> n >> m >> sx >> sy;
    bfs(sx, sy);
}
int main()
{
    init();
    system("pause");
    return 0;
}

最小生成树

普里姆算法(找点)

//邻接矩阵的数据类型
#define MAXV 50 //最大顶点数
typedef struct
{
	int no;//顶点编号
	//InfoType info;//顶点的其他信息
}VertexType;//顶点类型
typedef struct
{
	int edges[MAXV][MAXV];//邻接矩阵的边数组
	int n, e;//顶点数,边数
	VertexType vexs[MAXV];//存放顶点信息
}MGraph;//图邻接矩阵类型
#define INF 32767//表示无穷大
void Prim(MGraph g, int v)//普利姆算法(参数:邻接矩阵,起点(即第一个生成的点,可随便取))
{
	int lowcost[MAXV], closest[MAXV], i, min, j, k;
 
	/***初始化lowcost数组,closest数组(即从起点开始设置lowcost数组,closest数组相应的值,以便后续生成使用)***/
	for (i = 0; i < g.n; i++)//赋初值,即将closest数组都赋为第一个节点v,lowcost数组赋为第一个节点v到各节点的权重
	{
		closest[i] = v;
		lowcost[i] = g.edges[v][i];//g.edges[v][i]的值指的是节点v到i节点的权重
	}
 
	/**********************************开始生成其他的节点*********************************/
	for (i = 1; i < g.n; i++)//接下来找剩下的n-1个节点(g.n是图的节点个数)
	{
 
		/*****找到一个节点,该节点到已选节点中的某一个节点的权值是当前最小的*****/
		min = INF;//INF表示正无穷(每查找一个节点,min都会重新更新为INF,以便获取当前最小权重的节点)
		for (j = 0; j < g.n; j++)//遍历所有节点
		{
			if (lowcost[j] != 0 && lowcost[j] < min)//若该节点还未被选且权值小于之前遍历所得到的最小值
			{
				min = lowcost[j];//更新min的值
				k = j;//记录当前最小权重的节点的编号
			}
		}
 
		/****************输出被连接节点与连接节点,以及它们的权值***************/
		printf("边(%d,%d)权为:%d\n", closest[k], k, min);
 
		/***********更新lowcost数组,closest数组,以便生成下一个节点************/
		lowcost[k] = 0;//表明k节点已被选了(作标记)
		//选中一个节点完成连接之后,作数组相应的调整
		for (j = 0; j < g.n; j++)//遍历所有节点
		{
			/* if语句条件的说明:
			 * (1)g.edges[k][j] != 0是指k!=j,即跳过自身的节点
			 * (2)g.edges[k][j]是指刚被选的节点k到节点j的权重,lowcost[j]是指之前遍历的所有节点与j节点的最小权重。若g.edges[k][j] < lowcost[j],则说明当前刚被选的节点k与节点j之间存在更小的权重,则需要更新
			 * (3)有人会问:为什么只跳过掉自身的节点(即k==j),而不跳过所有的已选节点?当然我们可以在if语句条件中增加跳过所有的已选节点的条件(即lowcost[j] == 0),而在本程序中我们只跳过了自身的节点?(注意:我们假设图中的边的权值大于0)但其实不是,g.edges[k][j] < lowcost[j]条件已包含跳过所有的已选节点,原因是在邻接矩阵中权值为0是最小的,即g.edges[k][j]>=0,而已选节点满足lowcost[j] == 0,则已选节点j是不满足g.edges[k][j] < lowcost[j],则会被跳过
			 */
			if (g.edges[k][j] != 0 && g.edges[k][j] < lowcost[j])
			{
				//更新lowcost数组,closest数组
				lowcost[j] = g.edges[k][j];//更新权重,使其当前最小
				closest[j] = k;//进入到该if语句里,说明刚选的节点k与当前节点j有更小的权重,则closest[j]的被连接节点需作修改为k
			}
		}
	}
}

克鲁斯卡尔算法(找边)

#include <bits/stdc++.h>
using namespace std;

struct edge//纪录边的信息
{
    int start;
    int end;
    int weight;
};

//克鲁斯卡尔算法
void Kruskal(int data[10][3])
{
    int data2[10][2] = {0};
    multiset<int> st1;
    vector<edge> vec;//存储所有边的信息
    edge temp;
    for (int j = 0; j < 10; ++j)//初始化所有边的信息
    {
        temp.start = data[j][0];
        temp.end = data[j][1];
        temp.weight = data[j][2];
        vec.push_back(temp);
    }
    //按权值排序
    sort(vec.begin(), vec.end(), [](const edge &e1, const edge &e2) -> bool
    { 
        return e1.weight < e2.weight ? true : false ;
    });
    for (int j = 0; j < 10; ++j)
    {
        //st1存放的是已经加入的边用于判断是否会形成环(如果起点和终点都在st1中则加入该边会形成环)
        if (st1.find(vec[j].start) != st1.end() && st1.find(vec[j].end) != st1.end())
            continue;
        else
        {
            data2[j][0] = vec[j].start;//data2存放的是组成最小生成树的边
            data2[j][1] = vec[j].end;
            st1.insert(vec[j].start);
            st1.insert(vec[j].end);
        }
    }
    for (int j = 0; j < 10; ++j)
    {
        if (data2[j][0] != 0)
            cout << data2[j][0] << " " << data2[j][1] << endl;
    }
}

int main(int argc, char const *argv[])
{
    int data[10][3] = //所有边的信息
    {
        {1,2,6},{1,6,12},
        {1,5,10},{2,3,3},
        {2,4,5},{2,6,8},
        {3,4,7},{4,6,11},
        {4,5,9},{5,6,16}
    };
    Kruskal(data);
    return 0;
}

Oil Deposits

The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits. GeoSurvComp works with one large rectangular region of land at a time, and creates a grid that divides the land into numerous square plots. It then analyzes each plot separately, using sensing equipment to determine whether or not the plot contains oil. A plot containing oil is called a pocket. If two pockets are adjacent, then they are part of the same oil deposit. Oil deposits can be quite large and may contain numerous pockets. Your job is to determine how many different oil deposits are contained in a grid.

Input

The input file contains one or more grids. Each grid begins with a line containing m and n, the number of rows and columns in the grid, separated by a single space. If m = 0 it signals the end of the input; otherwise 1 <= m <= 100 and 1 <= n <= 100. Following this are m lines of n characters each (not counting the end-of-line characters). Each character corresponds to one plot, and is either *', representing the absence of oil, or@’, representing an oil pocket.

Output

For each grid, output the number of distinct oil deposits. Two different pockets are part of the same oil deposit if they are adjacent horizontally, vertically, or diagonally. An oil deposit will not contain more than 100 pockets.

Sample Input

1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5 
****@
*@@*@
*@**@
@@@*@
@@**@
0 0 

Sample Output

0
1
2
2

题意:

GeoSurvComp地质调查公司负责检测地下油藏。GeoSurvComp一次与一个大的矩形区域一起工作,并创建一个网格,将网格划分为多个方块。然后分别分析每个地块,使用传感设备确定该地块是否含有油。含油的情节被称为口袋。如果两个口袋相邻,则它们是同一个油藏的一部分。油藏可能相当大,可能含有大量的口袋。你的工作是确定网格中包含多少个不同的油藏。

思路:

首先定义一个数组,为8个方向,然后进行搜索,从左上角的口开始进入,然后逐个进行深搜,每搜到一个油田就++。因为这个油田可能相当大,所以在进行八个方向都走的时候也要伴随着搜索,因为有可能所有的油田都是连在一起的,那也就只有一块油田。

Code

#include<iostream>
using namespace std;
char a[102][102];
int row,col;
int direction[8][2]={
	 {1,0},
    {1,1},
    {1,-1},
    {0,-1},
    {0,1},
    {-1,0},
    {-1,1},
    {-1,-1}
};
void dfs(int i,int j)
{
	a[i][j]='*';
	for(int k=0;k<8;k++)
	{
		int x=i+direction[k][0];
		int y=j+direction[k][1];
		 if (x>=1&&x<=row&&y>=1&&y<=col&&a[x][y]=='@')
		 	dfs(x,y);
	}
}
int main(){
	while((cin>>row>>col)&&(row!=0||col!=0))
    {
        int ans=0;
        for(int i=1;i<=row;i++)
            for (int j=1;j<=col;j++)
            cin>>a[i][j];
        for(int i=1;i<=row;i++)
            for(int j=1;j<=col;j++)
            if(a[i][j]=='@')
            {
                dfs(i,j);
                ans++;
            }
        cout<<ans<<endl;
    }
    return 0;
}

蜘蛛牌

蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么这些牌也跟着一起移动,游戏的目的是将所有的牌按同一花色从小到大排好,为了简单起见,我们的游戏只有同一花色的10张牌,从A到10,且随机的在一行上展开,编号从1到10,把第i号上的牌移到第j号牌上,移动距离为abs(i-j),现在你要做的是求出完成游戏的最小移动距离。

Input

第一个输入数据是T,表示数据的组数。
每组数据有一行,10个输入数据,数据的范围是[1,10],分别表示A到10,我们保证每组数据都是合法的。

Output

对应每组数据输出最小移动距离。

Sample Input

1
1 2 3 4 5 6 7 8 9 10

Sample Output

9

思路

dfs搜索,搜索的策略是每次枚举10张纸牌,看它可以放在哪张牌的上面

深搜一下,先枚举每次移动哪张牌,再判断一下当前的这张牌要移动到哪张牌上

注意:比如 2,3,4,1 …当要移动1这张牌时,发现2,3,4 已经移动过了,那 1 就要移动到5 上,因为前 4 张牌一定都移动到5 了!

一定剪枝优化。先判断再搜索

Code

#include<iostream>
#include<string.h>
#include<cstdlib>
using namespace std;
int pos_card[15];   //用来存储某张牌的位置
int mark[15]; //用来做标记用的,
int min_sum;  //用来记录最小的移动距离


void dfs(int k,int sum)     //进行了k次对牌的移动操作,最当前小移动距离为sum
{
    if(sum >= min_sum)       //进行剪枝操作,如果当前最小移动距离sum 已经大于 之前 移动9张所用的最小移动的距离min_sum
        return;             //此时 这种深的情况 就没必要进行下去了
    if(k == 9)              //这里 k==9 时就可以 就有一种方案出现了,因为 已经把 移动了九张牌的位置放好了(10这张牌不可以被移动,所以只需要移动九张牌)
    {
            min_sum = sum;
    }
    for(int i=1;i<10;i++)   //这里的 i 表示是牌上的数字(就是要被 移动的牌)
    {
        if(! mark[i])       //mark[i]=0 说明这张牌没有被移动过,那我们就可先选择 移动这张牌
        {
            mark[i] = 1;    //设标记,表明该牌已经被移动过了,不能在被移了
            for(int j=i+1;j<=10;j++)    //这里的 j 表示 ,将i这张牌移动到 j 这张牌的上面(这里 有点难理解为什么是 “把i这张牌移到j牌上”,我们可能会想万一 j>i+1 ,这个时候  该次移动操作就不符合“把牌移到比她大1的牌上”的规则吗,)
            {                           //不必这样担心,因为如果 j>i+1,我们在这里假设 i = 2  ,j = 6 ;j为6说明 i=3,i=4,i=5 这三张牌已经被移动过了(因为此时的mark[3~5]=1,mark[6]=0),而且一定是这三张牌已经依照规则 先被移动到 6这张牌上面(这点一定要搞明白),
                                        //此时 i这张牌要想有最小的位移 只能 被移到 6 这张牌的上面(因为在6这张牌的最上面为3这张牌)
                if(! mark[j])           // mark[j] 为0 说明这张牌没有被移动过,知识又可能 有其他的牌移动到 这张牌的上面
                {
                    dfs(k+1,sum+abs(pos_card[i] - pos_card[j]));
                    break;              //这里的 break也需要理解,这里的理解是要i这张牌找到一个 第一个放置位置,就不用再找放置位置了
                }
            }
            mark[i] = 0;    //回溯清除标记
        }
    }
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        memset(mark,0,sizeof(mark));
        min_sum = 1e9;
        for(int i=1;i<=10;i++)
        {
            int temp;
            cin>>temp;
            pos_card[temp] = i;     //pos_card[3] = 5; 3号牌的位置为5
        }
        dfs(0,0);
        cout<<min_sum<<endl;
    }
    return 0;
}

N皇后问题

在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法。

Input

共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;如果N=0,表示结束。

Output

共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。

Sample Input

1
8
5
0

Sample Output

1
92
10

题解:

这题比较经典了,数据结构实验课也上过这个题的实验,只不过当时要找出放的位置,比这个麻烦一点其实也不麻烦,找个数组存着放的位置就可以,if条件符合的时候不是++了,而是输出数组的元素。

dfs进行搜索,一行一行的放皇后,从当前行找放的列的位置,然后判断符合要求不(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。)不合适的话就另判断的flag为false,如果合适的话就进行下一行的放置。

Code

#include<iostream>
#include <cstdio>
#include <math.h>
using namespace std;
int ans, n;
int col[11];
/*
bool Dfs(V) { 
if( V 为终点)
return true;
if( V 为旧点)
return false;
将V标记为旧点;
对和V相邻的每个节点U { 
if( Dfs(U) == true)
return true;
}
return false; 
} 

int main()
{
将所有点都标记为新点;
起点 = 1
终点 = 8
cout << Dfs(起点); }

*/ 
void dfs(int row)//回溯搜索 
{
    if (row == n)//如果为终点则方法数+1 
    {
        ans++;
        return ;
    }
    for (int i = 0; i < n; i++)//循环找地row行皇后的列的位置 
    {
        col[row] = i;//第i列 
        bool flag = true;
        for (int j = 0; j < row; j++)
        {
            if (col[j] == col[row] || col[j] + j == col[row] + row || col[j] - j == col[row] - row)//看看位置合适不 
            {
                flag = false;
                break;
            }
        }
        if (flag)
        {
            dfs(row + 1);// 第row行 
        }
    }
}

int main()
{
    int a[11];
    for (n = 1; n <= 10; n++)
    {
        ans = 0;
        dfs(0);
        a[n] = ans;
    }
    while(scanf("%d", &n), n)
    {
        cout << a[n] << endl;
    }
    return 0;
}

套路

遇到类似的问题都可以用下面的代码套进去,几乎所有的都可以,无非多了路径用path记录一下就可以了,在无非多点剪枝操作和回溯的操作,需要什么把什么在加上就可以。

bool Dfs(V) { 
if( V 为终点)
return true;
if( V 为旧点)
return false;
将V标记为旧点;
对和V相邻的每个节点U { 
if( Dfs(U) == true)
return true;
}
return false; 
} 

int main()
{
将所有点都标记为新点;
起点 = 1
终点 = 8
cout << Dfs(起点); }

Agri-Net

Farmer John has been elected mayor of his town! One of his campaign promises was to bring internet connectivity to all farms in the area. He needs your help, of course.
Farmer John ordered a high speed connection for his farm and is going to share his connectivity with the other farmers. To minimize cost, he wants to lay the minimum amount of optical fiber to connect his farm to all the other farms.
Given a list of how much fiber it takes to connect each pair of farms, you must find the minimum amount of fiber needed to connect them all together. Each farm must connect to some other farm such that a packet can flow from any one farm to any other farm.
The distance between any two farms will not exceed 100,000.

Input

The input includes several cases. For each case, the first line contains the number of farms, N (3 <= N <= 100). The following lines contain the N x N conectivity matrix, where each element shows the distance from on farm to another. Logically, they are N lines of N space-separated integers. Physically, they are limited in length to 80 characters, so some lines continue onto others. Of course, the diagonal will be 0, since the distance from farm i to itself is not interesting for this problem.

Output

For each case, output a single integer length that is the sum of the minimum length of fiber required to connect the entire set of farms.

Sample Input

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0

Sample Output

28

题意

约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。

你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案

题解:

prim算法用于计算图边径长度已知的图,它所求的是将图中所有顶点相连接,所需要的最短路径的长度,prim算法适合计算稠密图,数据结构都学过不再说明,初始化一个顶点,先将一个顶点加入树中,剩余的将进入n-1次循环逐步加入树中。再加入树中后,需要进行松弛操作,更新dis【】数组,使dis【】数组中的数值始终是未加入树中的顶点到生成的树的最短距离。

具体步骤

图初始化,将图中点与点之间的距离初始化为无穷,点本身的距离为0.

输入边的长度

随机选择一个点加入树中

.下一步将进行n-1次循环,选择n-1个点加入树中,每次通过 j循环遍历所有的点判断距离,找出最短距离

最短距离加入 ans

Code

#include <stdio.h>
#include <string.h>
#define INF 1<<20

int map[101][101];
int dis[101];
int vis[101];

long long ans;
void prim(int n)
{
    int i,j;
    memset(dis,INF,sizeof(dis));
    vis[1] = 1;
    dis[1] = 0;
    for(i = 1;i<=n;i++)
    {
        dis[i] = map[1][i];
    }
    int min,pos;
    for(i = 1;i<=n-1;i++)
    {
       min = INF;
       for(j = 1;j<=n;j++)
       {
           if(!vis[j] && dis[j]<min)
           {
               min = dis[j];
               pos = j;
           }
       }
       vis[pos] = 1;
       ans += min;
       for(j = 1;j<=n;j++)
       {
           if(!vis[j] && dis[j]>map[pos][j])
           dis[j] = map[pos][j];
       }
    }
}
int main()
{
    int n;
    int i,j;
    while(scanf("%d",&n)!=EOF)
    {
        memset(vis,0,sizeof(vis));
        for(i = 1;i<=n;i++)
        {
            for(j = 1;j<=n;j++)
            {
                scanf("%d",&map[i][j]);
            }
        }
        prim(n);
        printf("%lld\n",ans);
        ans = 0;
    }
    return 0;
}

Jungle Roads

在这里插入图片描述

热带岛屿拉格里山的长老有一个问题。几年前,大量的外国援助资金被用于修建村庄之间的公路。但丛林无情地取代了道路,所以庞大的道路网络维护起来太昂贵了。长老会议必须选择停止维护一些道路。上面左边的地图显示了现在使用的所有道路以及每个月维护这些道路的aacms费用。当然,在所有的村庄之间需要有一些道路来进行维护,即使这条路不像以前那么短。长老会告诉长老委员会,他们每月在aacms上最少能花多少钱来维护连接所有村庄的道路。在上面的地图上,村庄被标记为A到I。右边的地图显示了维持最便宜的道路,每月216 aacms。你的任务是编写一个程序来解决这些问题。

Input

输入包含1到100个数据集,最后一行只包含0。每个数据集以一行仅包含数字n的行开始,n为村庄数,1 <n & lt;村庄用字母表的前n个字母标记,大写。每个数据集由n-1行完成,这些行以字母顺序的村庄标签开始。最后一个村庄没有队伍。每个村庄的每一行都以村庄标签开始,然后是一个数字k,表示从这个村庄到后面的字母表中的村庄的道路。如果k大于0,这条线将继续处理k条道路的数据。每条道路的数据是道路另一端的村庄标签,然后是道路aacms中的每月维护成本。维护成本将是小于100的正整数。行中的所有数据字段由单个空格分隔。道路网络总是允许所有村庄之间的旅行。这个网络永远不会超过75条道路。没有一个村庄有超过15条的道路通往其他村庄(在字母表中前后)。在下面的示例输入中,第一个数据集与上面的地图一致。

output

警告:强力检查每一组可能的道路的解决方案不会在一分钟的时间内完成。

Sample Input

9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
3
A 2 B 10 C 40
B 1 C 20
0

Sample Output

216
30


思路

克鲁斯卡尔算法

Code:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxE 75
#define maxN 30
int tot;
int ans;
int n;
struct edge{
    int n1,n2,val;
}e[maxE];
int pre[maxN];
bool cmp(edge a,edge b)
{
	return a.val<b.val;
}
int _find(int x)
{
    if(pre[x]==x) return x;
    pre[x]=_find(pre[x]);
    return pre[x];
}
void Kruskal()
{
    int k=0;
    ans=0;
    for(int i=1;i<=n;i++)   pre[i]=i;
    for(int i=1;i<=tot;i++)
    {
        int f1=_find(e[i].n1);
        int f2=_find(e[i].n2);
        if(f1!=f2)
        {
            ans+=e[i].val;
            pre[f1]=f2;
            k++;
            if(k==n-1) break;
        }

    }
    return;
}
int main()
{
    while(scanf("%d",&n))
    {
        if(n==0) break;
        tot=0;
        for(int i=1;i<n;i++)
        {
            char str[5];
            scanf("%s",str);
            int k;
            scanf("%d",&k);
            for(int j=1;j<=k;j++)
            {
                char str[5];
                int val;
                scanf("%s",str);
                scanf("%d",&val);
                int u=str[0]-'A'+1;
                tot++;
                e[tot].n1=i;
                e[tot].n2=u;
                e[tot].val=val;
            }
        }
        sort(e+1,e+1+tot,cmp);
        Kruskal();
        printf("%d\n",ans);
    }
    return 0;
}

LCA+倍增例题Connections between cities

第十次世界大战后,许多城市遭到严重破坏,我们需要重建这些城市。但是,某些需要的材料只能在某些地方生产。因此,我们需要把这些材料从城市运到城市。由于大部分道路在战争期间被完全摧毁,两个城市之间可能没有道路,也没有圆圈存在。
现在,你的任务来了。在为您提供道路状况后,我们想知道两个城市之间是否存在路径。如果答案是肯定的,则输出它们之间的最短路径。

输入

输入由多个问题实例组成。对于每个实例,第一行包含三个整数 n、m 和 c,2<=n<=10000,0<=m<m<1<c<=1000000。n 表示从 1 到 n 编号的城市数。在 m 线之后,每行有三个整数 i、j 和 k,表示城市 i 和城市 j 之间的道路,长度为 k。最后一行 c 行,两个整数 i, j 每行,指示城市 i 和城市 j 的查询。

输出

对于每个问题实例,每个查询一行。如果两个城市之间没有路径,输出"未连接",否则输出它们之间最短路径的长度。

示例输入

5 3 2
1 3 2
2 4 3
5 2 3
1 4
4 5

样品输出

Not connected
6


        
  

提示

Hint

Huge input, scanf recommended.
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<functional>
#define N 10050*2
using namespace std;
int n,m,num,cnt;
struct node
{
	int to,next,cost;
}e[N];
int head[N],dis[N],depth[N];
int pre[N],fa[250][N];
int vis[N];
void init()
{
	cnt=0;
	for(int i=1;i<=n;i++)//用并查集来查找两点是否在同一颗树上
		pre[i]=i;//初始化并查集
	memset(e,0,sizeof e);//初始化建边
	memset(head,-1,sizeof head);
	memset(dis,0,sizeof dis);//初始化树深,距离
	memset(depth,0,sizeof depth);
	memset(vis,0,sizeof vis);
	memset(fa,-1,sizeof fa);
 
}
int getfa(int x)//并查集
{
	if(x==pre[x])
		return x;
	else
		return pre[x]=getfa(pre[x]);
}
void merge(int u,int v)//合并到同一颗树上
{
	int t1,t2;
	t1=getfa(u);
	t2=getfa(v);
	if(t1!=t2)
		pre[t1]=t2;
}
void addedge(int u,int v,int w)//建边
{
    e[cnt].to=u;
    e[cnt].cost=w;
    e[cnt].next=head[v];
    head[v]=cnt++;
}
void dfs(int u,int f)//搜索树,更新距离,树深
{
	vis[u]=1;
	for(int i=head[u];~i;i=e[i].next)
	{
		int To=e[i].to;
		if(To==f||vis[To])	continue;
		dis[To]=dis[u]+e[i].cost;
		depth[To]=depth[u]+1;
		fa[0][To]=u;
		dfs(To,u);
	}
}
void solve()//树上倍增法
{
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
			dfs(i,-1);
	}
	for(int i=0;i+1<15;i++)
	{
		for(int j=1;j<=n;j++)
			if(fa[i][j]<0)
				fa[i+1][j]=-1;
			else
				fa[i+1][j]=fa[i][fa[i][j]];
	}
}
int lca(int u,int v)//LCA倍增法在线查询
{
	if(depth[u]>depth[v])
		swap(u,v);
	for(int i=0;i<=15;i++)
	{
		if((depth[v]-depth[u])>>i&1)
			v=fa[i][v];
	}
 
	if(u==v)
		return v;
	for(int i=15;i>=0;i--)
	{
		if(fa[i][u]!=fa[i][v])
		{
			u=fa[i][u];
			v=fa[i][v];
		}
	}
	return fa[0][v];
}
int main()
{
	int i,a,b,c;
	while(scanf("%d%d%d",&n,&m,&num)!=EOF)
	{
		init();
		for(i=1;i<=m;i++)
		{
			scanf("%d%d%d",&a,&b,&c);
			addedge(a,b,c);
			addedge(b,a,c);
			merge(a,b);
		}
		solve();
		for(int i=1;i<=num;i++)
		{
			scanf("%d%d",&a,&b);
			int t1=getfa(a);
			int t2=getfa(b);
			if(t1==t2)//在同一颗树上
				printf("%d\n",dis[a]+dis[b]-2*dis[lca(a,b)]);
			else
				printf("Not connected\n");
		}
	}
	return 0;
}

首先是查看两者之间是否有路,使用并查询集

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int f[5000];
int fun(int x)
{
	return x==f[x]?x:fun(f[x]);
}
int main()
{
	int n,m,p;
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++)f[i]=i;//初始化
	while(m--)
	{
		int x,y;
		cin>>x>>y;
		int l=fun(x);
		int r=fun(y);
		if(l!=r)f[l]=r;//判断是否相等,如果不相等,就把l的祖先设置为r,合并。	
	}
	for(int i=1;i<=p;i++)
	{
		int x,y;
		cin>>x>>y;
		if(fun(x)==fun(y))//判断测试样例
		{
			cout<<"Yes"<<endl;
		}
		else cout<<"No"<<endl;
	}
}
 

测试:

5
3
2
1 3
2 4
5 2
1 4
No
4 5
Yes

通过LCA和倍增来找到两者之间的最近公共祖先,然后得出两者之间最短的距离。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NoteLoopy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值