【算法每日一练]-图论(保姆级教程篇15 )#虫洞(模板题) #无序字母对 #旅行计划 #最优贸易

目录

今日知识点:

两两点配对的建图方式,检查是否有环

无向图欧拉路径+路径输出

topo+dp求以i为终点的游览城市数

建立分层图转化盈利问题成求最长路


        

        

虫洞(模版题)

        

思路:
首先分析一下:第一是如何去建图,其次是如何找方案

找方案的话就直接暴力配对吧(题上的数据量不大,肯定要暴力),然后就是建图要保证即要方便图的还原(因为配对后还要回溯呢),又要方便跑图(每次建好都要跑一次看看是否存在一个循环),最后就是坐标范围非常大啊,所以要巧妙一点。

首先:

我们对这些黑洞位置排序,只关注同行的,同行中能从A黑洞走到B黑洞的就标记一下A能到B(使用唯一的ID号映射)。

之后:

dfs选择配对方案,这个dfs的写法相当于两两点匹配的模版了,建议熟背。我们要保证组内是升序的来去重,那么起点是k时,终点必须比k大;然后下一组匹配的起点至少也得是k+1,然后重复操作即可。

然后两两匹配的建图:

最好是g[i]=k,g[k]=i 这样建图(因为是两两配对,所以每个起点最多只有一个尾点)。

最后是检查函数cycle:

从起点一直走,走到走过的就可以停止了

#include <bits/stdc++.h>
using namespace std;
int ans,n,to[20],vis[20],g[20];
struct node{int x,y;}p[20];

bool cmp(node a,node b){
	return (a.y<b.y)||(a.y==b.y&&a.x<b.x);
}

int cycle(int x){
	while(to[x]){//走到下一个黑洞,如果有的话
		if(vis[x])return 1;
		vis[x]=1;
		x=g[to[x]];//终点变成起点(如果有的话)
	}
	return 0;
}
void dfs(int k){
	if(k>n){
		int f=0;
		for(int i=1;i<=n;i++){//直接从黑洞号开始就行
			memset(vis,0,sizeof(vis));
			f|=cycle(i);//这里之所以这么写,是为了防止被标记过1后又被0覆盖掉
		}
		ans+=f;
		return ;
	}
	if(g[k])dfs(k+1);
	else {
		for(int i=k+1;i<=n;i++){//组内升序
			if(g[i])continue;
			g[i]=k;g[k]=i;//设置两个黑洞的关系
			dfs(k+1);//组间起点升序
			g[i]=g[k]=0;//清除两个黑洞之间的关系
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>p[i].x>>p[i].y;
	sort(p+1,p+1+n,cmp);
	for(int i=1;i<=n-1;i++){
		if(p[i].y==p[i+1].y) to[i]=i+1;//把相邻且在同一行的标记在一起
	}
	dfs(1);
	cout<<ans;
	
}

首先是方案配对,然后是建图策略(至多两两配对),最后到检查循环都是非常精妙的 ,值得细看

         

        

无序字母对 

思路: 

这里就可以发现实际上就是在找欧拉路,首先每个字符就是代表的图中的某一个点,底下输入的字符串
就代表两点之间有连通,构造字符串就是在找输出一笔画回路,明白这个代码就很简单了

 下面是代码:基于无向图得欧拉路径问题+路径输出

无向图得欧拉路径:连通图,且没有度为奇数的节点(欧拉回路) 或 只有两个2个度为奇数的节点 。

因为此题要求的是欧拉路径,可能没有奇数度的节点,那么此时自然需要有多种方案可以选择,我们需要选择字典序最小的开始输出;也可能有奇数度的节点,那么此时是仅有一种方案的,所以我们要进行判断。

#include <bits/stdc++.h>
using namespace std;
const int N=300;
int g[N][N],fa[N],du[N],m;
char ans[N*N],s[N];
int find(int x)
{
    if(x!=fa[x]) fa[x]=find(fa[x]);
    return fa[x];//返回祖先 
}

void dfs(int x){
	for(int i=0;i<N;i++)
		if(g[x][i]){
		g[x][i]=g[i][x]=0;//取过了这个字母就清空,避免走环 g=0相当于vis=1
		dfs(i);//之所以g[i][x]也等于0,作用相当于避免走父子边回去了
	}
	ans[m--]=x;
}

int main()
{
	cin>>m;
    for(int i=0; i<N; i++) fa[i]=i;
    for(int i=1; i<=m; i++){
    	scanf("%s",s);
    	g[s[0]][s[1]]=g[s[1]][s[0]]=1;
        du[s[0]]++;du[s[1]]++;
        int f1=find(s[0]),f2=find(s[1]);//合并建树
        fa[f1]=f2;
	}
//判断是否连通
	int tmp=0;
	for(int i=0;i<N;i++)
		if(fa[i]==i&&du[i])tmp++;
	if(tmp!=1){
		cout<<"No Solution";return 0;
	}
//是否存在欧拉路径	
	int f=0,rt=0;
	for(int i=0;i<N;i++){
		if(du[i]&1){
			f++;	
			if(!rt)rt=i;//有奇数度的节点就要选此点开始
		}
	}
	if(f&&f!=2){
		cout<<"No Solution";return 0;
	}
//没有奇数度的节点就按照字典序最小开始输出路径
	if(!rt){
		for(int i=0;i<N;i++){//按照ASCII找最小的起点rt
			if(du[i]){
				rt=i;break;
			}
		}
	}
	dfs(rt);
	cout<<ans;

}

        

         

旅行计划

思路:

题上问以i点为终点的最多游览的城市数。非常类似之前说过的“食物链计数”那道题。

设置f[i]表示以i为终点的最多的游览城市数。那么从入度为0的点开始进行正向topo即可。

顺便再补充一个方向:如果设置f[i]表示以i为起点的最多的游览城市数。那么肯定不能正向topo。

这个时候需要用dfs进行回溯式topo处理。

#include<bits/stdc++.h> 
using namespace std;
const int N=100005;
vector <int>ve[N];
queue<int> q;
int n,m,ans,f[N],in[N],out[N];

int main(){
	cin>>n>>m;int x,y;
	for(int i=1;i<=m;i++){
		cin>>x>>y;
		ve[x].push_back(y);
		in[y]++;out[x]++;
	}
	for(int i=1;i<=n;i++){
		if(in[i]==0){q.push(i);f[i]=1;}
	}
	while(q.size()){//进行拓扑排序
		int cur=q.front();q.pop();
		for(int i=0,sz=ve[cur].size();i<sz;i++){
			int v=ve[cur][i];
			f[v]=f[cur]+1;in[v]--;
			if(in[v]==0) q.push(v);
		}		
	}

	for(int i=1;i<=n;i++)
	cout<<f[i]<<'\n';
	
	
	return 0;
}

        

         

最优贸易

 

思路:

 每个点都可以不卖不买,买,或卖这3种状态,那么分层图自然最合适。

最上面的层之间不论怎么跑动一定不会赚钱或亏钱。只有在层之间移动才能赚钱或亏钱。

也就是层内关系权值为0,层间非0.

然后就转化成spfa求最长路即可。 

#include <bits/stdc++.h> 
using namespace std;
const int N=1e5+5;
int cnt,head[N*3];
int dis[N*3];
bool vis[N*3];
struct Edge{ int to,w,next;}e[250000*3];
void add(int u,int v,int w) { e[++cnt]=(Edge){v,w,head[u]}; head[u]=cnt;}
void spfa(int s)
{
    memset(dis,-0x3f,sizeof(dis));
    queue<int>Q;
    dis[s]=0;vis[s]=1;
    Q.push(s);
    while(!Q.empty())
    {
		int u=Q.front(); Q.pop();
		vis[u]=0;
	    for(int i=head[u];i;i=e[i].next)
	    {
	        int v=e[i].to,w=e[i].w;
	        if(dis[v]<dis[u]+w) {
	        	dis[v]=dis[u]+w;
				if(vis[v])continue;
				Q.push(v);vis[v]=1;	
			}
	    }
    }    
}

int main()
{
	int n,m,price;
	cin>>n>>m; 
	for(int i=1;i<=n;i++){
		cin>>price;
		add(i,i+n,-price);//在层之间建立关系
		add(i+n,i+n*2,price);
	}
    int u,v,c;
    for(int i=0;i<m;++i)
    {
    	cin>>u>>v>>c;
    	add(u,v,0);add(u+n,v+n,0);add(u+2*n,v+2*n,0);//建立 分层图(层内图)
    	if(c==2){
    	add(v,u,0);add(v+n,u+n,0);add(v+2*n,u+2*n,0);
		}
    }
    spfa(1);
    printf("%d",dis[n+2*n]);
    return 0;
}

然后做完这道题,可以很明显发现和之前做过的“飞行路线”一题很像。那道题中是层内关系的权值非0,层间的关系权值为0,最后在最下面的层找答案即可。本题刚好反过来。

版权声明:本文为博主作者:希望你变强啊原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/m0_69478376/article/details/135429121

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2024年4月22日
下一篇 2024年4月22日

相关推荐