《冬训周报四》

并查集

概念与介绍

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。

并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。

作用

1.将两个集合合并

2.询问两个元素是否在一个集合当中

基本原理

对于每个集合,我们使用一棵树来表示。树根的编号是整个集合的编号。

集合中每个结点存储它的父节点,fa[x]表示x的父节点。

三个问题

1.如何判断集合编号(树根):fa[x] == x;

2.如何求x的集合编号:while(fa[x] != x) x= fa[x];

3.如何合并两个集合:假设a是第一个集合的编号,b是第二个集合的编号,则fa[a] = b;

路径压缩

如果直接使用 while(fa[x] != x) x= fa[x]; 寻找根节点,那么它的时间复杂度与树的长度成正比,非常容易超时。

所以在实现的过程中,我们可以使用一个非常巧妙地方法—路径压缩。

压缩前

int find(int x){
     if(fa[x]!=x) x = find(fa[x]);
     return x;
}//递归实现,容易TLE

压缩后

int find(int k)
{
    if(id[k] != k) id[k] = find(id[k]);
    return id[k];
    //每次找到根节点之后,在其回溯过程中
    //将路径上的节点的父节点更新为根节点,大大降低后续查询次数 
}

 

 例题

P3367 【模板】并查集

P1551 亲戚

BFS应用

介绍

广度优先搜索:在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作

一个最直观经典的例子就是走迷宫,我们从起点开始,找出到终点的最短路程,很多最短路径算法就是基于广度优先的思想成立的。

概念代码实现

将初始状态推入队列

当队列不空的时候,取队列中的元素,扩展

初始化队列Q;
 Q = {起点};
 标记s;
 while(Q非空)
 {
      取Q队首元素u;
      u出队;
      if(u==目标状态)
      {
         ……
      }
      else
      {
         所有与u相邻且未被访问的点进入队列;
        标记u为已访问;
      }
 }

过程图

 

 

 

最小生成树

介绍

现在假设有一个很实际的问题:我们要在n个城市中建立一个通信网络,则连通这n个城市需要布置n-1一条通信线路,这个时候我们需要考虑如何在成本最低的情况下建立这个通信网?
于是我们就可以引入连通图来解决我们遇到的问题,n个城市就是图上的n个顶点,然后,边表示两个城市的通信线路,每条边上的权重就是我们搭建这条线路所需要的成本,所以现在我们有n个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。(引)

即可以认为

一个图中可能存在多条相连的边,我们一定可以从一个图中挑出一些边生成一棵树这仅仅是生成一棵树,还未满足最小,当图中每条边都存在权重时,这时候我们从图中生成一棵树(n - 1 条边)时,生成这棵树的总代价就是每条边的权重相加之和。

 

 

 一个有N个点的图,边一定是大于等于N-1条的。图的最小生成树,就是在这些边中选择N-1条出来,连接所有的N个点。这N-1条边的边权之和是所有方案中最小的。

实现最小生成树的两种算法

prim(普利姆算法)和Kruskal(克鲁斯卡尔算法);

由于我自己对这两种算法还不能说有着深刻的理解,在这里放上两位大佬的算法分析

最小生成树详解(模板 + 例题)_潘小蓝的博客-CSDN博客_最小生成树

数据结构--最小生成树详解_Ouyang_Lianjun的博客-CSDN博客_数据结构最小生成树

例题(摘自大佬)

题目

 两种方法代码实现

#include <iostream>
#include <algorithm>

using namespace std;

const int N=1e5+10,M=2*N,INF=0x3f3f3f3f;

int p[N]; //并查集操作,通过该数组来存储父节点,当下标和其数组对应值相等时,该点便为根节点 
int n,m;

struct Edge
{
    int a;
    int b;
    int c;
    
    bool operator< (const Edge &W) const //建立一个存有边和两端点的结构体数组,并通过边的权重来比较大小 
    {
        return c<W.c;
    } 
}edges[M];

int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges,edges+m); 
    //将结构体数组通过边的权重从小到大来排序,这样优先遍历的是最小的边
    //某两点的最小边必定会优先被操作,并且通过并查集可以来判断两点是否已经进行连接操作,排除较大重边的影响 
    
    for(int i=1;i<=n;i++) p[i]=i; //并查集初始化操作 
    
    int sum=1,res=0;
    for(int i=0;i<m;i++)
    {
        int a=edges[i].a,b=edges[i].b,c=edges[i].c;
        a=find(a),b=find(b);
        
        if(a!=b) //如果两点的父节点相同,便表示这两点已经相连,由于边是从小到大遍历,故本次更新操作可以忽略 
        {
            sum++; //记录点的个数,初始值为1,每连接一个点边加1 
            res+=c; //每连接一个点边加上两点之间的权重 
            p[a]=b;
        }
    }
    
    if(sum==n) return res; //若相连的点最终有n个,则表示存在最小生成树,返回最小边权和 
    else return INF; //否则不存在最小生成树 
    
}

int main()
{
    cin>>n>>m;
    
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edges[i]={a,b,c};
    }
    
    int t=kruskal();
    
    if(t==INF) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    
    return 0;
}

/*
  由n个点和n-1条边生成的无向连通子图被称为生成树,其中边的权值之和最小的生成树即为最小生成树 
 */

代码实现

#include <iostream>
#include <cstring>

using namespace std;

const int N=510; 

bool st[N]; //将生成树看成一个集合,在集合中即为true,不在即为false 
int dist[N],g[N][N]; //dist数组是点到该集合的距离,g数组是两点之间的距离 
int n,m,res;

int prim()
{
    memset(dist,0x3f,sizeof(dist));
    
    for(int i=0;i<n;i++) //因为最小生成树要加入所有点,故要遍历n次 
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j]&&(t==-1||dist[t]>dist[j])) //在未加入生成树的点集合中选择一个点 
            {                                     //且该点距离集合的距离最小,符合最小生成树的边权之和最小的特性 
                t=j;
            }
        }
        
        st[t]=true; //选出点后便加入最小生成树集合 
        if(i&&dist[t]==0x3f3f3f3f) return dist[t]; //如果某点不是第一个加入最小生成树中的点,且该点的值为初始化的0x3f3f3f3f,即该点不能与其他点相连,故不能形成最小生成树 
        if(i) res+=dist[t]; //如果该点不是第一个点,则将该点与最小生成树集合之间的边的权重加入结果中 
        
        for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]); //每次最小生成树集合中加入新的点后,都更新其他点与集合的距离,本质上是更新未加入集合的点,但由于特判增加复杂度,故直接遍历所有点 
    }
    
    return res; //若未发现不与集合连通的点,则返回点之间的边权重之和 
}

int main()
{
    memset(g,0x3f,sizeof(g));
    
    cin>>n>>m;
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    
    int t=prim();
    
    if(t==0x3f3f3f3f) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    
    return 0;
}

 

补题

清楚姐姐打怪升级

题目

 

 

 

 

 

 代码实现

#include<bits/stdc++.h>
#define N 100100
#define ll long long
using namespace std;
ll hx[N],n,h[N],v[N],t,a;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin >> n >> t >> a;
    for(ll i=0;i<n;i++)
    {
     cin >> h[i] >> v[i];    
     hx[i] = h[i];    
    }
    ll ans=0;
    bool f=0;
        for(ll i=0;i<n;i++)
         {
                  if(v[i]*t >= a && hx[i] > a)
                  {
                   f=1;
                   break;            
                  }
                else
                {
                    if(hx[i]<=a) ans++;
                    else
                    {
                        hx[i] -= a;
                        ans++;
                        ans += hx[i]/(a-v[i]*t);
                        if(hx[i]%(a-v[i]*t)!=0)
                         ans++;                    
                    }
         }
         
         if(f) cout << "-1" << endl;
         else  cout << (ans-1)*t+1 << endl;
    return 0;
}

清楚姐姐的01背包

题目

 

 

 

 代码实现

#include <bits/stdc++.h>
using namespace std;
int main(){
    int T = 1;
    while(T--){
        int n,m;
        cin>>n>>m;
        int v[110]; 
        int w[110];
        long long dp[110];
        for(int i=1;i<=n;i++){
            cin>>w[i]>>v[i];
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j) continue;
                for(int k=m;k>=w[j];k--){
                    dp[k] = max(dp[k] , dp[k-w[j]] + v[j]);
                }
            }
            long long ans = dp[m]-dp[m-w[i]] - v[i] + 1;
            if(ans<0) cout<<0<<endl;
            else cout<<ans<<endl;
        }
    }
    return 0;
}

 

posted @ 2023-02-05 19:05  Ke_scholar  阅读(15)  评论(0编辑  收藏  举报