图论学习

文章目录

  • 1.dfs和bfs(这好像属于搜索)
  • 2.最短路
  • 3.拓扑排序
  • 4.树以及树的应用
    • 4.1并查集
      • 4.1.1种类并查集
      • 4.1.2带权并查集
    • 4.2缩点与割点
    • 4.3 最小生成树
      • 4.3.1 prime算法
      • 4.3.2 k算法

1.dfs和bfs(这好像属于搜索)

bfs是浪费空间节省时间,dfs是浪费时间节省空间。
dfs模板

// 数独 sudoku 

#include 

using namespace std;

int P[9][9];
bool flag = false;

// 处理输入 
void input(){
	char t;
	cout << "输入棋盘,空用 0 表示\n";
	for(int i = 0; i < 9; i ++){
		for(int j = 0; j < 9; j ++){
			cin >> t;
			P[i][j] = t - '0';
		}
	}
}

// 处理输出 
void output(){
	for(int i = 0; i < 9; i ++){
		for(int j = 0; j < 9; j ++){
			
			if(P[i][j]){
				cout << P[i][j];	
			}else{
				cout << " ";
			}
			
			if(j%3 == 2) cout << "  ";
		}
		if(i%3 == 2) cout << endl;
		cout << endl;
	}
	
}

// 判断即将放入的 是否符合条件 
bool check(int n, int num){
	
	// 行 
	for(int i = 0; i < 9; i ++){
		if(P[n/9][i] == num){
			return false;
		}
	} 
	
	// 列 
	for(int i = 0; i < 9; i ++){
		if(P[i][n%9] == num){
			return false;
		}
	} 
	
	// 宫
    int x = n / 9 / 3 * 3;
    int y = n % 9 / 3 * 3;
    for(int i = 0; i < 3; i ++){
    	for(int j = 0; j < 3; j ++){
    		if(P[x+i][y+j] == num){
    			return false;
    		}
    	}
    } 
    
    return true;
}

// [n/9][n%9]
void dfs(int n){
	
	// 成功 
	if(n > 80){
		flag = true;
		return;
	}
	
	int x = n/9, y = n%9;
	// 有值 跳过 
	if(P[x][y]){
		dfs(n+1);
		return;
	}
	// 遍历 
	for(int i = 1; i <= 9; i ++){
		// 判断 
		if(check(n, i)){
			// 判断成功 赋值 
			P[x][y] = i;
			dfs(n+1);
			// 退出时判断是否完成  完成时退出 
			if(flag){
				return;
			}
			// 未完成 重置棋盘 
			P[x][y] = 0;
		}
	}
}

int main(){
	input();
	cout << endl << endl; 
	dfs(0);
	output();
	return 0;
} 


/*

测试用例 

170004000
360810054
008050109
007035048
000000270
000008601
000060080
700000000
000403016

*/


bfs模板
P1331 海战

#include
#define intn long long
#define _0for(i, a) for(int i = 0; i < (a); ++i)
#define _1for(i, a) for(int i = 1; i <=(a); ++i)
using namespace std;
int a1x,a1y;
int a2x,a2y;
int ans=0;
int bx[5]={0,0,0,1,-1};
int by[5]={0,1,-1,0,0};
char a[1005][1005];
int n,m;
struct node
{
	int x,y;
};
int bfs(int x,int y)
{
	int ans=1;
	queue<node> q;
	node p;
	p.x=x,p.y=y;
	q.push(p);
	while(!q.empty())
	{
		int nowx=q.front().x,nowy=q.front().y;
		a[nowx][nowy]='.';
		for(int i=1;i<=4;i++)
		{
			int newx=bx[i]+nowx,newy=by[i]+nowy;
			if(newx>=1&&newy>=1&&newx<=n&&newy<=m&&a[newx][newy]=='#')
			{
				q.push(node{newx,newy});
				a[newx][newy]='.';
				a1x=min(newx,a1x);
				a1y=min(newy,a1y);	
				a2x=max(newx,a2x);
				a2y=max(newy,a2y);
				ans++;
			}
		}
		q.pop();
	}
	return ans;
} 
main(void)
{
	
	while(cin>>n>>m)
	{
		int ans=0,flag=1;
		for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
		}
		for(int i=1;i<=n;i++)
		{
		
		for(int j=1;j<=m;j++)
		{
			if(a[i][j]=='#')
			{
				a1x=i;a2x=i;a1y=j;a2y=j;
				if(bfs(i,j)==(a2x-a1x+1)*(a2y-a1y+1))
				{
					ans++;
				}
				else
				{
					flag=0;
					break;
				}
			}
		}
			if(flag==0)break;
		}
		if(flag)
		{
			printf("There are %d ships.",ans);
		}
		else
		{
			printf("Bad placement.");
		}
	}
}



2.最短路

我的另一篇文章

3.拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

操作:不断枚举入度为0的点入队更新入度。
P1347 排序

#include
#define intn long long
using namespace std;
queue<pair<int ,int> >q;
vector<int >g[50];
int ind[50];
int indz[50];
int main()
{
	int n,m;
	char a,t,b;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>a>>t>>b;
		q.push(make_pair(a-'A'+1,b-'A'+1));//存储m组排序序 
	}
	for(int k=1;k<=m;k++)//枚举排序 
	{
		int ind0_num=0;
		int flag=1;
		pair<int,int> now=q.front();q.pop();
		g[now.first].push_back(now.second);//存边 
		indz[now.second]++;//每次 进行更新入度 
		queue<int>qq;
		for(int i=1;i<=n;i++)
		{
			ind[i]=indz[i];//使用更新后的入度 
			if(ind[i]==0)
			{
				qq.push(i);// 将入度为零的点入队 
			
				ind0_num++;//找到入度为零的点的个数,如果多于1,说明有些关系没有确定 
			}
		}
		if(ind0_num!=1)
		{
		 	flag=0;
		}
		int cnt=0;
		int ans[30];
		while(!qq.empty())//拓扑排序 
		{
			int a=qq.front();
			qq.pop();
			ans[++cnt]=a;//存储答案 		
			ind0_num=0;		
			for(int i=0;i<g[a].size();i++)
			{
				ind[g[a][i]]--;//更新入度 
				if(ind[g[a][i]]==0)
				{
					qq.push(g[a][i]);//入队 
					ind0_num++;		
				}
			}
			if(ind0_num>1)
			{
				flag=0;
			}
		}
		if(cnt!=n)//如果cnt!=n,说明有环 
		{
			printf("Inconsistency found after %d relations.",k);
			return 0;
		}
		else
		{
			if(flag==0)//如果flag=0,说明有点的排序未确定 
			{
				continue;
			}
			else
			{
				printf("Sorted sequence determined after %d relations: ",k);
				for(int i=1;i<=n;i++)
				{
					printf("%c",ans[i]+'A'-1);
				}
				printf(".");
				return 0;
			}
		}
	}
	printf("Sorted sequence cannot be determined.");
}



4.树以及树的应用

4.1并查集

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

#include
using namespace std;
int i,j,k,n,m,s,ans,f[10010],p1,p2,p3;
//f[i]表示i的集合名
int find(int k){
	//路径压缩
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
}
int main()
{
    cin>>n>>m;
    for(i=1;i<=n;i++)
        f[i]=i;//初始化i的老大为自己
    for(i=1;i<=m;i++){
        cin>>p1>>p2>>p3;
        if(p1==1)
            f[find(p2)]=find(p3);
            //p3打赢了p2
        else
            if(find(p2)==find(p3))
            //是否是一伙的
                printf("Y\n");
            else
                printf("N\n");
    }
    return 0;
}

P2024 [NOI2001]食物链

4.1.1种类并查集

参考文章
食物链
这道题是要求维护x吃y,y吃z,z吃x的关系,那么我们将并查集扩大三倍,表示三个族群,则可以得到相互的关系(x,y)同类,(x,y+n)x吃y,(x+n,y+2n),x吃y,(x+2n,y)x吃y

int f[150005];
int find(int x)
{
	if(f[x]==x)return x;
	else
	return f[x]=find(f[x]);
}
void merge(int x,int y)
{
	int fx=find(x);
	int fy=find(y);
	f[fx]=fy;	
}
main(void)
{
	int n,k,jia=0;
	int x,y,cmd;
	
	cin>>n>>k;
	int p=3*n;
	for(int i=1;i<=3*n;i++)f[i]=i;
	_1for(i,k)
	{
		cin>>cmd>>x>>y;
		if(x>n||y>n)jia++;
		else if(cmd==2&&(x==y||find(x)==find(y)||find(y)==find(x+n)))jia++;
		else if(cmd==1&&(find(x)==find(y+n))||(find(y)==find(x+n)))jia++;
		else
		{
			if(cmd==1)
			{
				merge(find(x),find(y));
				merge(find(x+n),find(y+n));
				merge(find(x+2*n),find(y+2*n));
					
			}else 
			{
				merge(find(x),find(y+n));
				merge(find(x+n),find(y+2*n));
				merge(find(x+2*n),find(y)); 
			}
		}
	}
	cout<<jia;
}



4.1.2带权并查集

带权并查集是a与f[a]直接存在某种可以运算的关系的并查集。
上个题也可以用带权并查集来解决,0表示同类,1表示吃,2表示被吃,进行运算取模可以简便的进行关系的运算。

const int maxn=50010;
int f[maxn];
int ref[maxn];//0同类 1:吃:2被吃 
int find(int x)
{
	if(f[x]==x)return x;//寻找祖先过程中维护与祖先的关系
	int t=f[x];
	f[x]=find(f[x]);
	ref[x]=(ref[x]+ref[t])%3;
	return f[x]; 
}
void merge(int x,int y,int re)
{
	f[x]=y;
	ref[x]=re;
}
main(void)
{
	int n,k,jia=0;
	cin>>n>>k;
	_1for(i,n)f[i]=i;
	_0for(p,k)
	{
		int re,x,y;
		cin>>re>>x>>y;
		if(x>n||y>n)jia++;
		else if(re==2&&x==y)jia++;
		else if(find(x)==find(y))//已经有了关系,检查关系是否冲突 
		{
			if(re-1!=(ref[x]-ref[y]+3)%3)jia++; 
		}
		else
		{
			int newre=(re-1+ref[y]-ref[x]+3)%3;
			merge(find(x),find(y),newre); 
		}
	
	}
	cout<<jia;
}



经典题目 银河英雄传说
经过思考终于自己做出了这个带权并查集的题目
题意是将30000列舰队进行排列,如M,1,2是将1所在列排在2所在列的后边,要求输出两个舰队之间有几个舰队。
分析:很显然是一个并查集的题目,与一般并查集不同,要求输出一a,b的数值关系。很容易想到,加一个数组维护x与祖先的距离关系,写在find函数里。如寻找x的祖先时,即舰队队列的最前一个元素时,有x-y-z,x的祖先变为z,那么就要更新x与祖先距离nf[x]为原先到y的加上y到z的。
在建立关系时,如将a所在队列连接到b所在队列,一般是将a的祖先连接到b的祖先,但是nf数组还没有更新,nf[a],nf[b]在寻找a-x和b-y时已经更新的,而nf[x]一开始是0,连接y以后是y所在队列的数量所以要加一个数组num[i]表示i所在队列的舰队数量,nf[x]=num[y],因为每次都会找到祖先元素,所以只更新num[y]就可以了。

#include
#define intn long long
int f[30005];
int nf[30005];
int num[30005];
int find(int x)
{
	if(f[x]==x)return x;
	int t=f[x];
	f[x]=find(f[x]);
	nf[x]=nf[x]+nf[t];
	return f[x];
}
main(void)
{
	int t,a,b;
	char cmd;
	cin>>t;
	_1for(i,30000)f[i]=i,num[i]=1;
	_1for(p,t)
	{
		cin>>cmd>>a>>b;
		int x=find(a);
		int y=find(b);
		if(cmd=='M')
		{
			f[x]=y;
			nf[x]=num[y];
			num[y]=num[y]+num[x];
		}
		else
		{
			if(x!=y)
			printf("-1\n");
			else
			{
				printf("%d\n",abs(nf[a]-nf[b])-1);
			}
		}
	//	debug(y);debug(nf[y]);debug(num[y]);
	} 
}




4.2缩点与割点

缩点
tarjan算法

void tarjan(int x)
{
	dfn[x]=low[x]=++num;
	ins[x]=1;
	st.push(x);

	for(int i=0;i<g[x].size();i++)
	{
		int q=g[x][i];
		if(dfn[q]==0)
		{
			tarjan(q);
			low[x]=min(low[q],low[x]);
		}
		else if(ins[q]==1)
		{
			low[x]=min(low[x],dfn[q]);
		}
	}
	if(dfn[x]==low[x])
	{
		numb++;
		int p;
		do
		{
			p=st.top();
			st.pop();
			bl[p]=numb;
			nums[numb]++;
			ins[p]=0;
		}
		while(x!=p);
	}
}

4.3 最小生成树

4.3.1 prime算法

使用邻接矩阵,一开始将mp初始化为inf,然后更新路径,从某一点prime函数,更新这一点的dist,进入for循环, 只需要n-1次循环,寻找最小dist,加入最短路径的点后更新dist。最终得到结果。

const int inf=999999;
int mp[400][400];
int n;
int visit[400];
int dist[400];
int ans=0;
int prime(int cur)
{
	int index;
	int sum=0;
	visit[cur]=1;
	_1for(i,n)dist[i]=mp[cur][i];
	for(int i=1;i<=n-1;i++)
	{
		int mincost=inf;
		_1for(j,n)
		{
			if(!visit[j]&&dist[j]<mincost)
			{
				mincost=dist[j];
				index=j;
			}
		}
		debug(index);
		visit[index]=1;
		sum+=mincost;
		ans=max(ans,mincost);
		_1for(j,n)
		{
			if(!visit[j]&&dist[j]>mp[index][j])
			{
				dist[j]=mp[index][j];
			}
		}
	}
	return sum;
}
main(void)
{
	int m,x,y,z;
	cin>>n>>m;
	_1for(i,n)
	_1for(j,n)
	mp[i][j]=inf;
	_1for(i,m)
	{
		cin>>x>>y>>z;
		mp[x][y]=z;
		mp[y][x]=z; 
	}
	prime(1);
	cout<<n-1<<" "<<ans;
}



4.3.2 k算法

需要用到并查集,使用vector存图,将路径从小到大排列 ,从1开始枚举路径,如果路径的点没有经过,加一条,更新并查集,直到加了n-1条边为止。

int f[1000];
int ans=0;
int ansm=0;
struct node
{
	int u,v,w;
}a[100005];
bool cmp(node a,node b)
{
	return a.w<b.w;
}
int find(int x)
{
	if(f[x]==x)return x;
	return f[x]=find(f[x]);
}
main(void)
{
	int n,m;
	cin>>n>>m;
	_1for(i,n)f[i]=i;
	_1for(i,m)
	{
		cin>>a[i].u>>a[i].v>>a[i].w;
	}
	sort(a+1,a+1+m,cmp);
	int cnt=0;
	_1for(i,m)
	{
		if(cnt==n-1)break; 
		int x=find(a[i].u);
		int y=find(a[i].v);
		if(x!=y)
		{
			f[x]=y;
			ans+=a[i].w;
		
			cnt++;
			ansm=max(ansm,a[i].w);
		}
	}
	cout<<n-1<<" "<<ansm;	
}



你可能感兴趣的:(acm)