图论加深

一些较基础的知识点:

例一 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;
}
posted @ 2022-09-11 14:02  Code_AC  阅读(28)  评论(2编辑  收藏  举报