图论加深
一些较基础的知识点:
例一 P1196 银河英雄传说
带权并查集,比较简单。
设 \(dis_i\) 表示点 \(i\) 到并查集根节点的距离。
那么我们只要在 find 和 merge 的时候各统计一下,再弄一个 \(siz_i\),就可以做了。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e4+5;
int t;
int fa[MAXN],dis[MAXN],siz[MAXN];
inline int find(int x)
{
if(x==fa[x])
return fa[x];
int f=find(fa[x]);
dis[x]+=dis[fa[x]];
return fa[x]=f;
}
inline void merge(int x,int y)
{
int a=find(x),b=find(y);
if(a==b)
return;
fa[a]=b;
dis[a]+=siz[b];
siz[b]+=siz[a];
return;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>t;
for(register int i=1;i<=30000;i++)
fa[i]=i,siz[i]=1;
while(t--)
{
char op;
cin>>op;
if(op=='M')
{
int a,b;
cin>>a>>b;
merge(a,b);
}
else
{
int a,b;
cin>>a>>b;
int x=find(a),y=find(b);
if(x!=y)
printf("-1\n");
else
printf("%d\n",abs(dis[a]-dis[b])-1);
}
}
return 0;
}
例二 CF687D Dividing Kingdom II
同样是一道并查集的题目,只不过带了一点贪心。
题意:
1.对于所有边,编号在 \([l,r]\) 内的,将他们的点划分到两个集合 \(S1,S2\);
2.对于每两个集合 \(S1,S2\) 取最大权值;
3.对于所有划分取最小值。
分析:
1.我们贪心的想,先将边的权值从大到小排序,那么每次拆的就会尽可能大;
2.然后我们将 \(fa\) 数组开两倍,将 \(a,b+n\) 合并,将 \(a+n,b\) 合并,就可以保证他们不在同一集合;
3.若操作无法执行,那么就证明 \(a,b\) 已在同一集合内。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e3+5;
const int MAXM=5e5+5;
struct edge
{
int st,en,len,id;
}e[MAXM];
int n,m,q;
int fa[MAXN<<1];
inline int find(int x)
{
if(x==fa[x])
return fa[x];
return fa[x]=find(fa[x]);
}
inline void merge(int x,int y)
{
int a=find(x),b=find(y);
if(a!=b)
fa[a]=b;
return;
}
inline bool cmp(edge x,edge y)
{
return x.len>y.len;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(register int i=1;i<=m;i++)
{
cin>>e[i].st>>e[i].en>>e[i].len;
e[i].id=i;
}
sort(e+1,e+m+1,cmp);
while(q--)
{
int l,r,ans=-1;
cin>>l>>r;
for(register int i=1;i<=n*2;i++)
fa[i]=i;
for(register int i=1;i<=m;i++)
if(e[i].id>=l && e[i].id<=r)
{
int a=find(e[i].st),b=find(e[i].en);
if(a==b)
{
ans=e[i].len;
break;
}
else
{
merge(e[i].st+n,e[i].en);
merge(e[i].st,e[i].en+n);
}
}
printf("%d\n",ans);
}
return 0;
}
例三 P3623 免费道路
首先我们给出如下定义:
必选的鹅卵石路指将所有水泥路全部选完后图仍未连通,需要选的鹅卵石路,其他鹅卵石路为可选的鹅卵石路。
分析:
1.我们可以先将所有水泥路尝试连成生成树,边数为 \(tot(tot\leqslant n-1)\);
2.然后跑一遍 \(\text{Kruscal}\),检查连通性并统计出必选的鹅卵石路的条数 \(cnt=n-1-tot\);
3.之后先连必选的鹅卵石路,然后连可选的连到 \(k\) 条,剩下的用水泥路连,最后跑一遍 \(\text{Kruscal}\) 即可。
那么我们就有以下几种 No Solution 的情况:
1.整个图根本就不连通;
2.必选的鹅卵石的数量大于 \(k\),也就是说无法将所有必选的全选上;
3.所有鹅卵石路不足 \(k\)。
所以说这道题思路不难,但要打满分很难。
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
struct edge
{
int x,y,z;
}e[MAXN],ans[MAXN];
inline bool cmp1(edge a,edge b)
{
return a.z>b.z;
}
inline bool cmp2(edge a,edge b)
{
return a.z<b.z;
}
int n,m,k;
int fa[MAXN];
int cnt,tot;
inline void init()
{
cnt=tot=0;
for(register int i=1;i<=n;i++)
fa[i]=i;
return;
}
inline int find(int x)
{
if(x==fa[x])
return fa[x];
return fa[x]=find(fa[x]);
}
inline void merge(int x,int y)
{
int a=find(x),b=find(y);
if(a!=b)
fa[a]=b;
return;
}
inline bool check()
{
int f=find(1);
for(register int i=2;i<=n;i++)
if(find(i)!=f)
return true;
return false;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
for(register int i=1;i<=m;i++)
cin>>e[i].x>>e[i].y>>e[i].z;
init();
sort(e+1,e+m+1,cmp1);
for(register int i=1;i<=m;i++)
{
int a=find(e[i].x),b=find(e[i].y),c=e[i].z;
if(a!=b && !c)
tot++,e[i].z=-1;
merge(e[i].x,e[i].y);
}
if(tot>k || check())
{
printf("no solution\n");
return 0;
}
init();
sort(e+1,e+m+1,cmp2);
for(register int i=1;i<=m;i++)
{
int a=find(e[i].x),b=find(e[i].y),c=e[i].z;
if(a!=b)
if(c==1 || tot<k)
{
ans[++cnt]=e[i];
fa[a]=b;
if(c<1)
tot++,e[i].z=0;
}
}
if(tot<k || check())
{
printf("no solution\n");
return 0;
}
for(register int i=1;i<=cnt;i++)
{
if(ans[i].z==-1)
ans[i].z=0;
printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].z);
}
return 0;
}
欧拉路径与欧拉回路
1.无向图的欧拉路径
在一张无向图 \(G\) 中,存在一条路径可以不重复地经过每一条边。
2.欧拉回路
欧拉回路是一条特殊的欧拉路径,起点和终点重合。
3.无向图存在欧拉路径的充分必要条件
度数为奇数的点的个数要么是 \(0\) 个,要么是 \(2\) 个。
4.实现方法
-
判定是否有解
-
选取一个度数为奇数的点作为起点
-
\(\text{dfs}\) 搜索每一条边并标记
-
存储经过的顶点必须在递归之后
5.有向图的欧拉路径与欧拉回路
与上面相同。
6.判定方法
-
入度-出度为 \(1\) 的点数恰好为 \(1\) 个
-
出度-入度为 \(1\) 的点数恰好为 \(1\) 个
或者
- 所有点入度=出度。
汉密尔顿环问题
在一张无向图 \(G\) 中,存在一条路径可以不重复地经过每一个点。
这个问题目前没有非暴力的解法,属于 \(\text{NP}\) 难题。
例一 P7771 欧拉路径
欧拉路径板子题。
这道题卡时间卡得有点紧,需要加些优化。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
// char buf[1<<21],*p1=buf,*p2=buf;
// #define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){ if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
vector<int>e[MAXN];
int n,m;
int ru[MAXN],chu[MAXN];
int stk[MAXN],top;
int head[MAXN];
inline void dfs(int x)
{
for(int i=head[x];i<e[x].size();i=head[x])
{
head[x]=i+1;
dfs(e[x][i]);
}
stk[++top]=x;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
e[x].push_back(y);
chu[x]++,ru[y]++;
}
for(int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());
int id=1,cnt1=0,cnt2=0;
bool flag=true;
for(int i=1;i<=n;i++)
{
if(chu[i]!=ru[i]) flag=false;
if(chu[i]-ru[i]==1) cnt1++,id=i;
if(ru[i]-chu[i]==1) cnt2++;
}
if((!flag) && !(cnt1==cnt2 && cnt2==1))
{
printf("No\n");
return 0;
}
dfs(id);
while(top) printf("%d ",stk[top--]);
return 0;
}
例二 P2731 [USACO3.3]骑马修栅栏 Riding the Fences
欧拉回路板子题。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=505;
// char buf[1<<21],*p1=buf,*p2=buf;
// #define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){ if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
int m;
int e[MAXN][MAXN];
int stk[MAXN],top;
int du[MAXN<<1];
inline void dfs(int x)
{
for(int i=1;i<=500;i++)
if(e[x][i])
e[x][i]--,e[i][x]--,dfs(i);
stk[++top]=x;
return;
}
int main()
{
m=read();
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
e[x][y]++,e[y][x]++;
du[x]++,du[y]++;
}
vector<int>V;
for(int i=1;i<=500;i++)
if(du[i]&1) V.push_back(i);
if(!V.size()) dfs(1);
else dfs(min(V[0],V[1]));
while(top) printf("%d\n",stk[top--]);
return 0;
}