【樱花飘落的速度是每秒5厘米】

 导语

我们都知道,BFS的性质可以运用在边权为1的最短路求解中。

单源边权值为1的最短路(实在不行只能自己打一个模板了)

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int N=110;
const int INF=0x3f3f3f3f;

int h[N],e[N],ne[N],idx;
int n,m;
int dist[N];
bool st[N]; 
queue<int> q;

void add(int a,int b){
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}

//求编号为1的点到编号为2的点的最短距离,数据假设满足条件 

int bfs(){
	q.push({1});
	st[1]=true;
	while(q.size()){
		int t=q.front();
		q.pop();
		if(t==2) break;
		for(int i=h[t];i!=-1;i=ne[i]){
			int j=e[i];
			if(!st[j]&&dist[j]>dist[t]+1){
				dist[j]=dist[t]+1;
				st[j]=true;
				q.push(j);
			}
		}
	}
	if(dist[2]==INF) return -1;
	else return dist[2];
}

int main(){
	cin>>n>>m;
	memset(dist,0x3f,sizeof dist);
	for(int i=0;i<m;i++){
		int x,y;
		cin>>x>>y; 
		add(x,y); add(y,x);
	}
	cout<<bfs();
	return 0;
} 

重点不是这个。

这篇文章是想写一下BFS的变形。

BFS+拆点

无线网络

精简题干后:

有n个已经修好的站点,和m个可以修建的站点,每两个站点之间的距离小于r就可以相互通信,求在新修站点小于k个的情况下,第一个站点到第二个站点的最小距离。边权为1。

和导语中的模板对比,这里变化了什么?

1.存在一部分站点是可以选择的,只是选择的个数不超过k

2.边没有直接给出来,需要我们判断哪些点直接存在边

3.从第一点可以看出,到达第二个站点存在1~k中情况。引入dist[N][N],第二个参数代表这条路径需要新修的站点个数

拆点:

就是上文的第三点。

BFS可以放好多东西进去维护啊

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#define x first
#define y second
using namespace std;
const int N=220,M=N*N;

typedef long long LL;
typedef pair<int,int> PII;
queue<PII> q;
int n,m,k,r;
int dist[N][N];
int h[N],e[M],ne[M],idx;

struct V{
	int x,y;
}v[N];

bool check(int i,int j){
	LL dx=v[i].x-v[j].x;
	LL dy=v[i].y-v[j].y;
	return dx*dx+dy*dy<=r*r;
}

void add(int a,int b){
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}

int bfs(){
	q.push({1,0});
	memset(dist,0x3f,sizeof dist);
	dist[1][0]=0;
	while(q.size()){
		PII t=q.front();
		q.pop();
		for(int i=h[t.x];i!=-1;i=ne[i]){
			int x=t.x,y=t.y;
			int j=e[i];
			if(j>n) y++;
			if(y<=k){
				if(dist[j][y]>dist[t.x][t.y]+1){
					dist[j][y]=dist[t.x][t.y]+1;
					q.push({j,y});
				}			
			}
		}
	}
	int res=1e8;
	for(int i=0;i<=k;i++){
		res=min(res,dist[2][i]);
	} 
	return res-1;
}


int main(){
	cin>>n>>m>>k>>r;
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;i++) cin>>v[i].x>>v[i].y;
	for(int i=n+1;i<=n+m;i++) cin>>v[i].x>>v[i].y;
	
	for(int i=1;i<=n+m;i++){
		for(int j=i+1;j<=n+m;j++){
			if(check(i,j)){
				add(i,j);add(j,i);
			}
		}
	}
	cout<<bfs();
	return 0;
} 

多源BFS

最优配餐

简言之:求多个点到一个点的最短路径的最小值。

想法:可以看成一个超级原点经过多个点到达目标点的最短路径,其中超级原点到多个点的边权都是0,所以在BFS中直接放入多个点就可以了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue> 
#define x first
#define y second
using namespace std;
const int N=1010;

typedef long long LL;
typedef pair<int,int> PII;
bool g[N][N];
int dist[N][N];
int n,m,k,d;
queue<PII> q;
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};

struct Tar{
	int x,y,c;
}tar[N*N];

void bfs(){
	while(q.size()){
		PII t=q.front();
		q.pop();
		for(int i=0;i<4;i++){
			int x=t.x+dx[i],y=t.y+dy[i];
			if(x<1||x>n||y<1||y>n||g[x][y]) continue;
			if(dist[x][y]>dist[t.x][t.y]+1){
				dist[x][y]=dist[t.x][t.y]+1;
				q.push({x,y});
			}
		}
	}
}


int main(){
	scanf("%d%d%d%d",&n,&m,&k,&d);
	memset(dist,0x3f,sizeof dist);
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		dist[x][y]=0;
		q.push({x,y});
	}
	for(int i=0;i<k;i++)
		scanf("%d%d%d",&tar[i].x,&tar[i].y,&tar[i].c); 
	
	while(d--){
		int x,y;
		scanf("%d%d",&x,&y);
		g[x][y]=true;
	}
	
	bfs();
	
	LL res=0;
	for(int i=0;i<k;i++){
		res+=dist[tar[i].x][tar[i].y]*tar[i].c;
	}
	printf("%lld",res);
	return 0;
}

一个简单数字排序引发的血案

数字排序

题干很简单,给定n个数,统计他们出现的次数,然后按照出现次数递减的顺序输出,如果出现次数相等,数字小的先输出。

做法也很简单,一个结构体一个自定义比较函数。

#include<iostream>
#include<algorithm> 
using namespace std;
 
const int N=1010;
int n,x;
//可以离散化,但我不会 
 
struct Data{
    int id;
    int num;
};
struct Data data[N];
bool cmp(struct Data a,struct Data b){
    if(a.num!=b.num) return a.num>b.num;
    else return a.id<b.id;
}
 
int main(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>x;
        data[x].id=x;
        data[x].num++;
    }
    sort(data,data+N,cmp);
    int i=0;
    while(data[i].num!=0){
        cout<<data[i].id<<" "<<data[i].num<<endl;
        i++;
    }
    return 0;
}

可是lf说这道题是因为数据范围小,这种类似桶排序的东西才可以过的。一直叫着去写map+优先队列。(其实离散化也可以,但我不会)

现在抽空看一下吧。

总体思路就是使用map<int,int> mp,进行数字统计,然后将map里面的元素放入优先队列,优先队列里的比较函数是可以自定义的。问题就出在重写<上。

priority_queue<> q默认大根堆,比较函数模板为less,为什么使用less却是大根堆呢?

//大根堆默认是less,我们越小越靠右,从左端取出最大元素
//重写<,相当于自定义优先级。 

当我们在结构体中重写<时:

	bool operator< (const node& n) const{
		if(cnt==cnt) return num>n.num;
		else return cnt<n.cnt;
	}

不免会产生疑惑

需求:次数不同时,次数大的放前面;相同时,数字小的放前面。

这里的<就可以看成优先级的一种关系,它定义了哪一个元素的优先级小。很明显比较次数时,次数越小,优先级越小。数字时,数字越大优先级越小。

写到这里我们大概明白了,这里重写的不是什么普普通通的小于符号,而是优先级判定标准。

树的直径

概念:一棵树上最短路的最大值 

两次dfs

找到任意一个点x距离它最远的点y,在从y点出发,找到离它最远的点,这两点之间的距离就是树的直径。

下面是y为直径一个端点的证明

大臣的旅费

#include<iostream>
#include<algorithm> 
#include<cstring>
using namespace std;
const int N=1e5+10,M=2e5+10;

int n;
int h[N],e[M],ne[M],w[M],idx;
int dist[N]; 

void add(int a,int b,int c){
	e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}

void dfs(int u,int father,int dis){
	dist[u]=dis;
	for(int i=h[u];i!=-1;i=ne[i]){
		int j=e[i];
		if(j!=father){
			dfs(j,u,dis+w[i]);
		}
	}
} 

int main(){
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);add(b,a,c);
	}
	dfs(1,-1,0);
	int r=1;
	for(int i=1;i<=n;i++)
		if(dist[i]>dist[r]){
			r=i;
		} 
	dfs(r,-1,0);
	for(int i=1;i<=n;i++)
		if(dist[i]>dist[r]){
			r=i;
		}
	int m=dist[r];
	printf("%lld",m*10+m*(m+1ll)/2);
	return 0;
}

在交这份代码的时候我陷入了MLE,我一度以为这种模拟的方式比不上struct+vector,结果发现是自己的边开小了(双边)。加了个M就过了。

不过struct+vector不用在意这些细节,也不错。

  网络延时


 

这道题也是树的直径。

我们已知根节点就是编号为1的点,就只需要找到的到根节点的最大和次大层数相加就可以啦。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e4+10;

int n,m,ans=0;
int p[N];
int h[N],e[N],ne[N],idx; 

int dfs(int u){
	int d1=0,d2=0;
	for(int i=h[u];i!=-1;i=ne[i]){
		int j=e[i];
		int d=dfs(j);
		if(d>=d1){
			d2=d1;
			d1=d;
		}
		else if(d>d2) d2=d;
	}
	ans=max(ans,d1+d2);
	return d1+1; 
}


void add(int a,int b){
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	for(int i=2;i<=n;i++){
		int x;cin>>x;
		add(x,i);
	}
	for(int i=n+1;i<=n+m;i++){
		int x;cin>>x;
		add(x,i);
	}
	dfs(1);
	cout<<ans;
	return 0;
}

写到这里,就不得不思考一下,为什么同样是数的直径,这道题可以这样写,推广一下呢?不能推广,也可以思考一下他的特殊性。

好像是不行的,特殊性:这里是一个树,故不存在环。就写这么多了,后续有空在补一下吧。图论真好玩

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值