导语
我们都知道,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;
}
写到这里,就不得不思考一下,为什么同样是数的直径,这道题可以这样写,推广一下呢?不能推广,也可以思考一下他的特殊性。
好像是不行的,特殊性:这里是一个树,故不存在环。就写这么多了,后续有空在补一下吧。图论真好玩