当前位置: 首页 > news >正文

CSP集训错题集 第八周 主题:基础图论

E

单源最短路径,让我捡回迪杰斯特拉。。。

上一把写迪杰斯特拉还是实验课:数据结构实验——基于 Dijsktra 算法的最短路径求解_编程题实训-实验5-基于dijsktra-CSDN博客。

我就不再提上面的s山了,现在我的写法优化了很多。这个算法的核心就两步:

1.比较di[lst].res+w和di[nxt].res,如果更小代表解更优,就更新。

更新不仅是di[nxt].res,而且di[nxt].pre也需要更新成lst。

2.比较di[i].res,寻找最小值,作为下一个点。

然后就是其它的建结构体、初始化等内容。

最后注意,可能有非连通的情况,比如这个点:

输入:

5 15 5
2 2 270
1 4 89
2 1 3
5 5 261
5 2 163
5 5 275
4 5 108
4 4 231
3 4 213
3 3 119
3 1 77
3 1 6
2 4 83
5 5 196
5 5 94
输出

166 163 2147483647 246 0 

所以,while终止的条件不应该是集齐所有点cnt==n,而是lst==-1,也就是下一个点不存在。

Code

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
struct node{bool vis;int res,pre;
}di[100005];
int n,m,s,u,v,w;
vector<vector<pair<int,int> > > a(100005);
void init();
void dijkstra();
int main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m>>s;init();for(int i=1;i<=m;i++){cin>>u>>v>>w;a[u].push_back({v,w}); }dijkstra();for(int i=1;i<=n;i++){cout<<di[i].res<<' ';}cout<<endl;return 0;
}
void init(){for(int i=1;i<=n;i++){di[i].vis=0;di[i].res=2147483647;di[i].pre=i;}
}
void dijkstra(){int cnt=0;//如果cnt=n那么遍历完,终止 di[s].res=0;//起始位置res等于0 int lst=s;//lst表示上一个的位置 //while(cnt<n)写法是错的 while(lst!=-1){//可能不是连通图 //标记lst已经被遍历过了 di[lst].vis=1;//lst上一个 nxt下一个 wei权值 for(int i=0;i<a[lst].size();i++){int nxt=a[lst][i].first;int wei=a[lst][i].second;if(di[lst].res+wei<di[nxt].res){di[nxt].res=di[lst].res+wei;di[nxt].pre=lst;}}int minres=2147483647,minpos=-1;for(int i=1;i<=n;i++){if(i==s) continue;if(di[i].vis==1) continue;//没有被遍历而且距离最短 if(di[i].res<minres){minpos=i;minres=di[i].res;}}lst=minpos;//下一个遍历的位置就是minpos的位置 //cout<<lst<<endl;//用来测试下一个点位 cnt++;} 
}

F

同样的题面,数据变大了。

关于一些疑问,D老师还是很权威的:

以这个为例:
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
理论输出:0 2 4 3

首先(0,1)入队,然后得到(2,2) (4,4) (5,3),都会入队。
但是(4,4) (5,3)显然不是最优解,
那么请问这是怎么过滤掉的?

有一些过程解不是最优解,会因为小根堆的排序而排到后面去。

然后因为vis标记,所以你从队列首部取出来的必定是最优解,等到你做完一圈最优解之后,尽管还有一些糟粕解留在队列里,但是因为vis=1被跳过。(比如第五步,这题里的[4,4][5,3])。

Code

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
struct node{bool vis;int res,pre;
}di[100005];
int n,m,s,u,v,w;
vector<vector<pair<int,int> > > a(100005);
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> >//存储的数据类型,底层容器,比较函数(小顶堆,最小元素在堆顶,先一后二) 
> pq;
void init();
void dijkstra();
int main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m>>s;init();for(int i=1;i<=m;i++){cin>>u>>v>>w;a[u].push_back({v,w}); }dijkstra();for(int i=1;i<=n;i++){cout<<di[i].res<<' ';}cout<<endl;return 0;
}
void init(){for(int i=1;i<=n;i++){di[i].vis=0;di[i].res=2147483647;di[i].pre=i;}
}
void dijkstra(){di[s].res=0;//起始位置res等于0 pq.push({0,s});//距离、节点  while(!pq.empty()){int lst=pq.top().second;//上一个元素 pq.pop();//一定要及时出队! if(di[lst].vis)continue;di[lst].vis=1;//lst上一个 nxt下一个 wei权值 for(int i=0;i<a[lst].size();i++){int nxt=a[lst][i].first;int wei=a[lst][i].second;if(di[lst].res+wei<di[nxt].res){di[nxt].res=di[lst].res+wei;di[nxt].pre=lst;pq.push({di[nxt].res,nxt});}}} 
}

G

变种dijkstra

有向图->无向图。"You are given a simple connected undirected graph......"

只有边权重->点也有权重。

其实想通了也很简单,把点权重和边权重加一起就行了。然后起始距离不是0而是第一个点权重。

    for(i64 i=1;i<=m;i++){cin>>u>>v>>w;a[u].push_back({v,w}); a[v].push_back({u,w});}di[s].res=b[s];//起始位置res等于0 pq.push({b[s],s});//距离、节点  if(di[lst].res+wei+b[nxt]<di[nxt].res){di[nxt].res=di[lst].res+wei+b[nxt];di[nxt].pre=lst;pq.push({di[nxt].res,nxt});}

H

这一题我比较明确是什么意思,但是想不到有什么高效解法,又去问了D老师。

原来可以把这张图看成树,先定一个节点(我定1),然后计算所有点距离根节点的距离。

然后又给出了一个公式:

距离 = depth[c] + depth[d] - 2 × depth[lca(c,d)]

距离%2==1 输出Road;距离%2==0 输出Town。

lca是什么?

实际上和lca无关,因为 2 × depth[lca(c,d)]%2==0 ,不影响结果。本来想学的,但是看着很抽象,什么“向上跳2^k步”,劝退,劝退。

写到这里,我发现我有点搞不清
vector<int> a[200005];
vector<vector<pair<int,int> > > a(100005);
这两玩意

#include<iostream>
#include<vector>
#include<queue>
#define i64 long long
using namespace std;
i64 n,q,u,v;
vector<i64> a[200005];
i64 dis[200005];
void bfs();
int main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>q;for(i64 i=1;i<=n-1;i++){cin>>u>>v;a[u].push_back(v);a[v].push_back(u);}bfs();while(q--){cin>>u>>v;i64 ans=dis[u]+dis[v];if(ans%2==1) cout<<"Road"<<endl;else if(ans%2==0) cout<<"Town"<<endl;}return 0;
}
void bfs()
{bool s[200005];queue<i64> q;q.push(1);s[1]=1;i64 depth=0;while(!q.empty()){q.push(0);++depth;while(q.front()!=0){i64 lst=q.front();q.pop();for(i64 i=0;i<a[lst].size();i++){i64 nxt=a[lst][i];if(s[nxt])continue;s[nxt]=1;dis[nxt]=depth;q.push(nxt);}}q.pop();}
}

I

bfs记层可解,但是对32错7。

然后我发现,居然是一个代码冗余带来的灾难???

如果三个节点,但是只有两条边,是(1,2)(2,1),仍成立。

纪律性这一块

#include<iostream>
#include<vector>
#include<queue>
#define i64 long long
using namespace std;
i64 n,m,u,v;
vector<i64> a[200005];
void bfs();
int main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m;for(i64 i=1;i<=m;i++){cin>>u>>v;a[u].push_back(v);}/*if(n>m){cout<<-1<<endl;return 0;}*///错误代码 bfs();return 0;
}
void bfs()
{bool s[200005]={0},flag=0;i64 depth=0;queue<i64> q;//s[1]=1;怀疑这点是不需要的 q.push(1);while(!q.empty()&&!flag){++depth;q.push(0);while(q.front()!=0&&!flag){i64 lst=q.front();q.pop();for(int i=0;i<a[lst].size();i++){int nxt=a[lst][i];if(s[nxt])continue;if(nxt==1)//找到目标1 flag=1;s[nxt]=1;q.push(nxt);}}q.pop();}while(!q.empty()){//队列全部放空,享受素质人生 q.pop();}if(flag) cout<<depth<<endl;else cout<<-1<<endl; return ;
}

J

并查集的思路

但是、对于每个连通分量,点数和边数如何统计?

我们可能想到,有那么多个点,但是只有几个连通分量,所以很难统计。可是实际上,往往都是空间换时间的过程,我们就开两个数组去存储边数和点数,就会方便很多。(这种思路很妙而且对于我这种初学者有点反直觉)

那么每两个点进来,就不仅是联合操作一次,而且边和点都要操作,实际上就是相加就可以了sz[x]+=sz[y]; edge[x]+=edge[y]+1;    

这题一开始WA了两次,是因为 edge[x]++ 错写成 edge[x]=y ,edge[x]+edge[y]+1 错写成 edge[x]=edge[y]+1。

#include<iostream>
#define i64 long long
using namespace std;
i64 n,m,u,v,ans=0;
i64 pre[200005]={0},sz[200005]={0},edge[200005]={0};
void init();
void unite();
i64 find(i64 num);
int main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m;init();for(i64 i=1;i<=m;i++){cin>>u>>v;unite();}for(i64 i=1;i<=n;i++){if(pre[i]==i){//自己 ans+=sz[i]*(sz[i]-1)/2-edge[i]; }}cout<<ans<<endl; return 0;
}
void init(){for(int i=1;i<=n;i++){pre[i]=i;sz[i]=1;//特别 }
}
void unite(){i64 x=find(v),y=find(u);if(x==y){edge[x]=y;//如果两个点祖宗一致,那么单纯的边增加,不会带来点的问题。 return ;}//如果祖宗不一样,那么说明两个图之间变连通,原来两个点的点、边数据现在合并到一个点y上 pre[y]=x;//y依靠于x,那么x是核心 sz[x]+=sz[y]; edge[x]=edge[y]+1;	return ;
}
i64 find(i64 num)
{if(pre[num]==num) return num;else return pre[num]=find(pre[num]);
}

K

n<=100,那么懒得vector了,直接上邻接矩阵。

学到现在,算法无非就是dfs bfs 并查集 dijkstra,而现在分析题面的能力越来越重要了。

分析这题:灾后重建,有的桥带权,但是没有被破坏,所以实际上这个权不应该被计算进去。

如果把这个权置为0,那么是不是也是一个最短路径dijkstra?

所以我们借用一个数组操作一下,让没有被破坏的边权值变0,有破坏的边权值正常显示,其它都是MX。那么这题就结束了。

#include<iostream>
#include<queue>
#define MX 10086
#define i64 long long
using namespace std;
struct node{bool vis;int res,pre;
}di[105];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> >
> pq;
i64 n,m,t,u,v,w,st=0,ed=0,ans=0;
i64 a[105][105]={0};
bool broken[105][105]={0};
void init();
void dijkstra();
int main(){ios::sync_with_stdio(0);cin.tie(0);init();cin>>st>>ed;dijkstra();return 0;
}
void dijkstra(){di[st].res=0;pq.push({0,st});while(!pq.empty()){i64 lst=pq.top().second;pq.pop();if(di[lst].vis)continue;di[lst].vis=1;for(i64 i=1;i<=n;i++){if(a[lst][i]==MX)continue;i64 wei=a[lst][i];i64 nxt=i;if(di[lst].res+wei<di[nxt].res){di[nxt].res=di[lst].res+wei;di[nxt].pre=lst;pq.push({di[nxt].res,nxt});}}}/*for(int i=1;i<=n;i++)cout<<di[i].res<<endl;*/cout<<di[ed].res<<endl;return ;
}
void init(){cin>>n>>m;//初始化全部是MX for(i64 i=1;i<=n;i++){di[i].res=MX;for(i64 j=1;j<=n;j++){a[i][j]=MX;}}//开始输入路径 for(i64 i=1;i<=m;i++){cin>>u>>v>>w;a[u][v]=w;a[v][u]=w;}//标记谁被破坏了 cin>>t;for(i64 i=1;i<=t;i++){cin>>u>>v;broken[u][v]=1;broken[v][u]=1;}//如果原来有路,而且没被破坏,道路置0 for(i64 i=1;i<=n;i++){for(i64 j=1;j<=n;j++){if(a[i][j]!=MX&&!broken[i][j]){a[i][j]=0;}}}//输出演示 /*for(i64 i=1;i<=n;i++){for(i64 j=1;j<=n;j++){cout<<a[i][j]<<' ';}cout<<endl;}*/return ;
}

M

学了数据结构或者离散数学的都能知道Prim和Kruskal(避圈法)算法。算法很好理解,但是避圈法太难实现,所以我用prim。

我找的题解在这:最小生成树算法 - 洛谷专栏

其实不用完整看这个题解,你看一遍Prim的演示图你就知道这算法是个啥意思了。和迪杰斯特拉不能说一模一样但是确实是很像。内核都是bfs更新。不过最短路径着重点是点,最小生成树着重点是边。所以造成了判定条件略微的不一样。

然后这题依旧是稳定发挥交上去无法AC,因为一开始感觉a[5005][5005]还行,懒得用vector,但是MLE了4个点,所以只好换vector了。

#include<iostream>
#include<queue>
#define MX 10086
#define i64 long long
using namespace std;
i64 n,m,u,v,w,res=0;
i64 dis[5005]={0};
vector<pair<i64,i64> > a[5005];
bool s[5005]={0};
priority_queue<//来个堆更方便排序 pair<i64,i64>,vector<pair<i64,i64> >,greater<pair<i64,i64> >
> q;
void init();
void prim();
bool check();
int main(){cin>>n>>m;init();for(i64 i=1;i<=m;i++){cin>>u>>v>>w;a[u].push_back({v,w});//可能存在重边 a[v].push_back({u,w});}prim();if(check()) cout<<res<<endl; else cout<<"orz"<<endl;return 0;
}
void init(){for(i64 i=1;i<=n;i++){dis[i]=MX;}return ;
}
void prim(){//把1作为根节点 dis[1]=0;q.push({0,1});//路径在前节点在后,堆根据这样排序 while(!q.empty()){i64 lst=q.top().second;//取出队首,能被取出来的一定是贪心最优解 i64 distance=q.top().first;q.pop();if(s[lst]==1)//如果被遍历过 continue;s[lst]=1; for(i64 i=0;i<a[lst].size();i++){i64 nxt=a[lst][i].first;i64 wei=a[lst][i].second;if(s[nxt]) continue;if(wei>=dis[nxt]) continue;dis[nxt]=wei;q.push({wei,nxt});}}return ;
}
bool check(){for(i64 i=1;i<=n;i++){if(dis[i]==MX){return 0;}res+=dis[i];}return 1;
}

N

我一开始以为,这又是一个复杂的一批的题目,打开洛谷,啊?橙题?啊?题解代码这么短?

首先看这题可能被这个路径给吓到了,但是实际上我们并不关注中间还要经过多少额外节点,这题直接就是一个M个点之间的最短路径求和问题,不就行了?

所以这个纯纯就是纸老虎。我们只需要求出每个点对每个点的最短路径就行了。而Floyd算法复杂度为O(n3),100的数据恰到好处不会TLE。那么a不断更新就可以了。

不过,身为菜鸟级选手,为啥floyd这么写捏?

中间这一行是个dp更新最小值,很好理解,难的就是怎么理解k在i和j的前面。

这个文章描述的很好,D老师描述的我倒是没怎么理解到。

https://blog.csdn.net/qq_43753724/article/details/129507989

#include<iostream>
#define MX 10086
#define i64 long long
using namespace std;
i64 a[105][105]={0}, b[10005]={0};
i64 n,m,ans=0;
void floyd();
int main(){cin>>n>>m;for(i64 i=1;i<=m;i++){cin>>b[i];}for(i64 i=1;i<=n;i++){for(i64 j=1;j<=n;j++){cin>>a[i][j];}}floyd();for(i64 i=2;i<=m;i++){ans+=a[b[i-1]][b[i]];}cout<<ans;return 0;
}
void floyd(){for(i64 k=1;k<=n;k++){for(i64 i=1;i<=n;i++){for(i64 j=1;j<=n;j++){a[i][j]=min(a[i][j],a[i][k]+a[k][j]);}}}return ;
}
http://www.dtcms.com/a/564663.html

相关文章:

  • 基于C语言 HTTP 服务器客户端的实验
  • 如何利用 Jupyter 从浏览器访问远程服务器
  • C语言基础知识点简单案例分享之二——C语言全知识点速查宝典
  • 怎么找网站局域网电脑做网站服务器
  • 一男一女做那个的动漫视频网站网站怎样推广 优帮云
  • hive常用命令
  • AWS + 飞天CMS:高性能内容站的云端搭建方案
  • 800G光模块:驱动AI与云计算高速互联的核心引擎
  • Python每日一练---第三天:删除有序数组中的重复项
  • U-Net 的输入与输出:通用场景与扩散模型场景解析
  • 李宏毅机器学习笔记39
  • 【代码随想录算法训练营——Day57(Day56周日休息)】图论——53.寻宝
  • PPT+配音生成带旁白的PPT演示视频
  • abp vnext cli无法正常使用,卡在 Checking extensions..,cli修改abp版本及.net版本
  • 萤石摄像头使用NAS作为存储
  • 2025江西省职业院校技能大赛(中职组)移动应用与开发竞赛样题
  • 建站公司哪家好在哪里推广比较好
  • 在 iOS 18 的照片应用,如何批量隐藏截屏?
  • OK3568 Android11 实现 App 独占隔离 CPU 核心完整指
  • 湖南网站建设公司 都来磐石网络泰安营销型网站建设公司
  • Oracle 如何计算 AWR 报告中的 Sessions 数量
  • JavaScript 流程控制语句
  • 走向专精:我的NLP特化算子开发之旅
  • 如何写prompt?prompt收集
  • 打工人日报#20251103
  • 技术文章大纲:设备如何“开口说话”?
  • CH585 高速 USB模拟 CDC串口应用示例
  • 2024/07 JLPT听力原文 问题四
  • 【AAOS】【源码分析】Car Location服务(二)- NMEA 数据
  • 如何建立国外网站搜索引擎优化岗位