图的练习
1.P1636 Einstein学画画 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
前置:
欧拉路(七桥问题):是否可从某个地方出发,经过每座桥一次,回到原来出发的地方?
相关图概念:
顶点的度,就是指和该顶点相关联的边数。
出度:有向图中从某顶点出发的边数。
入度:有向图中在某顶点结束的边数。
欧拉回路:
若恰通过图中每条边一次回到起点,则称该回路为欧拉(Euler)回路。具有欧拉回路的图称为欧拉图。
定理1:
一个无向图是欧拉图,当且仅当该图所有顶点度数都是偶数。
一个有向图是欧拉图,当且仅当该图所有顶点度数都是0(入度与出度之和)。
定理2:
存在欧拉回路的条件:图是连通的,且不存在奇点(顶点度数为奇数)欧拉路:
若从起点到终点的路径恰通过图中每条边一次(起点与终点是不同的点),则该路径称为欧拉路。
定理1:
存在欧拉路的条件:图是连通的,且存在2个奇点。如果存在2个奇点,则欧拉路一定是从一个奇点出发,以另一个奇点结束。
定理:一个连通图只可能有偶数个奇点。
解析:
通过欧拉路我们可以知道如果此题提供的图有两个奇点,那么我们便可以一笔画完。如果有多个奇点,我们可以将该图视为多个两奇点的图叠加而成,有多少个两奇点,便要画几次。所以我们统计图中奇点的个数,除二即可。
另外,如果图中没有奇点,那么该图是欧拉回路,可以直接一笔画成,需要特判。
代码:
#include<iostream> #include<cstdio> using namespace std; int du[200010]; int cnt; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { int a,b; scanf("%d%d",&a,&b); du[a]++;du[b]++; } for(int i=1;i<=n;++i) { if(du[i]%2) ++cnt;//统计奇点 } int ans=0; if(!cnt) ans=1;//欧拉回路 else ans=cnt/2; printf("%d",ans); return 0; }
2.P8654 [蓝桥杯 2017 国 C] 合根植物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
前置:并查集
并查集是一种可以动态维护若干个不重叠的集合,并支持合并与查找的数据结构。每一个集合都找出一个代表,该集合中的其他点都最终指向该代表。
解析:
题目中将多格植物并为一株,即用并查集将多格植物编号并为同一集合,每个集合有一个代表编号(初始代表编号为自身编号),处于不同编号下的就是不同集合,不同植株。
由于除了代表的编号还是自身,其他格子的编号都指向代表,所以最终统计有多少个编号指向自身的就有多少个集合。
代码:
#include<iostream> #include<cstdio> using namespace std; int fa[1000010]; int vis[1000010]; int sf(int x) { if(fa[x]==x) return x; else return fa[x]=sf(fa[x]); } void bing(int r1,int r2) { fa[r2]=r1; } int main() { int n,m;scanf("%d%d",&m,&n); int k;scanf("%d",&k); for(int i=1;i<=n*m;++i) fa[i]=i; for(int i=1;i<=k;++i) { int a,b; scanf("%d%d",&a,&b); int r1=sf(a),r2=sf(b);//查找各自的代表编号 if(r1!=r2) bing(r1,r2);//将二者并入同一集合 } int ans=0; for(int i=1;i<=m*n;++i) { if(fa[i]==i) ++ans; } printf("%d",ans); return 0; }
3.P3958 [NOIP2017 提高组] 奶酪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
解析:
同样用到并查集。先用并查集将相通的洞并入同一集合,如果与上下底面都相通的洞处于同一集合,那么就可以利用已有的空洞跑到最上面。
代码:
#include<iostream> #include<cstdio> #include<cmath> using namespace std; struct node { int x,y,z; }d[1010]; int di[1010],ding[1010]; int cnt1,cnt2; int fa[1010]; int sf(int x) { if(fa[x]==x) return x; else return fa[x]=sf(fa[x]); } int main() { int T;scanf("%d",&T); while(T--) { cnt1=cnt2=0; int n,h,r;scanf("%d%d%d",&n,&h,&r); for(int i=1;i<=n;++i) fa[i]=i; for(int i=1;i<=n;++i) { scanf("%d%d%d",&d[i].x,&d[i].y,&d[i].z); if(d[i].z-r<=0) di[++cnt1]=i;//与下底面接触 if(d[i].z+r>=h) ding[++cnt2]=i; //与上底面接触 } for(int i=1;i<n;++i) { for(int j=i+1;j<=n;++j) { double k=pow(d[i].x-d[j].x,2)+pow(d[i].y-d[j].y,2)+pow(d[i].z-d[j].z,2); if(sqrt(k)<=2*r)//两洞相通,并入同一集合 { int r1=sf(i),r2=sf(j); if(r1!=r2) fa[r2]=r1; } } } int ans=0; for(int i=1;i<=cnt1;++i) { for(int j=1;j<=cnt2;++j) { //同一集合且与上下都相通 if(sf(di[i])==sf(ding[j])) ans=1; } } if(ans==1) printf("Yes\n"); else printf("No\n"); } return 0; }
4.P1119 灾后重建 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
解析:
floyd的深层次运用。学习floyd时,三层循环表示直接从i到j与经过k点的从i到j相比,距离是否缩短。回到此题,初始从i村到j村间的距离已知,现在k村庄修好,我们需要判断dis[i][j]与dis[i][k]+dis[k][j]的大小,正契合floyd的思想。于是我们将三层循环中的最外层加上时间这一判断条件即可。
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<queue> using namespace std; int t[210]; int f[210][210]; int n,m; void floyd(int k) { for(int i=0;i<n;++i) for(int j=0;j<n;++j) if(f[i][j]>f[i][k]+f[k][j]) f[i][j]=f[j][i]=f[i][k]+f[k][j]; } int main() { scanf("%d%d",&n,&m); for(int i=0;i<n;++i) for(int j=0;j<n;++j) f[i][j]=100000; for(int i=0;i<n;++i) { scanf("%d",&t[i]);f[i][i]=0; } for(int i=1;i<=m;++i) { int x,y,z; scanf("%d%d%d",&x,&y,&z); f[x][y]=f[y][x]=z; } int q;scanf("%d",&q); int tot=0,cnt=0; for(int i=1;i<=q;++i)//由于按时间从小到大,直接遍问边答 { int a,b,ti; scanf("%d%d%d",&a,&b,&ti); while(t[tot]<=ti&&tot<n)//时间允许 { floyd(tot);++tot; } int ans; if(t[a]>ti||t[b]>ti) ans=-1; else if(f[a][b]==100000) ans=-1; else ans=f[a][b]; printf("%d\n",ans); } return 0; }
5.P2504 [HAOI2006]聪明的猴子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
解析:
借助并查集实现的krukal算法(最小生成树)。在生成是判断长边是否可跳过。
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<queue> using namespace std; struct node { int x,y; double val; }edg[100100]; int fa[10100]; int m,n; int a[10010],b[10010]; int d[5100]; int cnt=0; bool cmp(node a,node b) { return a.val<b.val; } int find(int x) { if(fa[x]==x) return x; else return fa[x]=find(fa[x]); } int main() { scanf("%d",&m); for(int i=1;i<=m;++i) scanf("%d",&d[i]); scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d%d",&a[i],&b[i]); for(int i=1;i<=n;++i) { for(int j=1;j<=n;++j) { if(i==j) continue; ++cnt; edg[cnt].x=i;edg[cnt].y=j; double val=pow(a[i]-a[j],2)+pow(b[i]-b[j],2); edg[cnt].val=sqrt(val); } } for(int i=1;i<=n;++i) fa[i]=i; sort(edg+1,edg+cnt+1,cmp); int k=0; double maxx=0; for(int i=1;i<=cnt;++i) { int x=find(edg[i].x),y=find(edg[i].y); if(x==y) continue; fa[y]=x; maxx=edg[i].val; ++k;if(k==n-1) break; } int ans=0; for(int i=1;i<=m;++i) if(d[i]>=maxx) ++ans; printf("%d",ans); return 0; }