首页 > 其他分享 >《冬训周报四》

《冬训周报四》

时间:2023-02-05 19:11:35浏览次数:45  
标签:int 冬训 最小 生成 fa 集合 节点 周报

并查集

概念与介绍

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

并查集的思想是用一个数组表示了整片森林(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;
}

 

标签:int,冬训,最小,生成,fa,集合,节点,周报
From: https://www.cnblogs.com/Gwzhizi/p/17093805.html

相关文章

  • 2023.1.30周报
    本周总结由于动态规划方面比较薄弱,所以本周决定刷关于动态规划的题目大主题动态规划小专题线性dp,区间dp,树状dp,背包题目完成情况每种类型各完成7道左右的题......
  • 《安富莱嵌入式周报》第301期:ThreadX老大离开微软推出PX5 RTOS第5代系统,支持回流焊的
    往期周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104祝大家开工大吉视频版:https://www.bilibili.com/video/BV1GT411o......
  • .NET周报【1月第4期 2023-01-28】
    由于微信公众号排版问题,建议大家在PC端浏览。国内文章C#很少人知道的科技https://blog.lindexi.com/post/C-很少人知道的科技.html本文来告诉大家在C#很少有人会发现......
  • 《安富莱嵌入式周报》第300期:几百种炫酷灯阵玩法, USB Web网页固件升级,波士顿动力整
    往期周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 祝大家春节快乐视频版:https://www.bilibili.com/video/BV1U......
  • 《冬训周报三》
    关于C++中ios::sync_with_stdio(false); 在C++中的输入和输出有两种方式,一种是scanf和printf,另一种是cin和cout,在#include<bits/stdc++.h>这个万能头文件下,这两种方式是......
  • SMU冬训营第三周周一
    A.Lucky?题意:给出一个六位数,如果它的前三位之和等于它的后三位之和,就输出"YES",否则输出"NO"。思路:测试样例里面有的六位数不是真正的六位数,有的是‘0’开头的,所以选择......
  • .NET周报【1月第3期 2023-01-20】
    这应该是2023年农历新年前的最后一篇.NET周报,再次预祝大家新年快乐!国内文章看我是如何用C#编写一个小于8KB的贪吃蛇游戏的https://www.cnblogs.com/InCerry/p/building-......
  • 寒假周报(三)
    寒假周报Date:2023年1月17日(周二)大致方向这一周把时间花在了学车上,就没怎么写题了,复习一下之前的知识。详细情况复习!!!学车!!!考试!!!总结顺利考完科目一!!!o( ̄︶ ̄)o(100分)(待更新,......
  • 周报三
    2023/1/15周报大主题:字符串&动态规划&数论本周总结:本周主要是对Treap的学习。Treap学习笔记:C.C--/Treap.mdatmaster·JoshuaZhengsurp/C.C--(github.com)之......
  • 2023.1.16周报
    本周总结:《算法竞赛》5.1、5.2,5.5、5.6,《算法竞赛进阶指南》0x53、0x54。大方向:动态规划小专题:区间DP、树形DP题目完成情况:div2、abc各一场。P2015(树形DP)、P1352......