首页 > 其他分享 >寒假集训Day9

寒假集训Day9

时间:2024-01-26 14:57:06浏览次数:23  
标签:nxt head Day9 int cnt 寒假 judge 集训 dis

前言

经过昨天的一天模拟赛,我成功坐牢5小时,13道题就会两道,所以我决定放弃每天的傻逼题和rating赛的题,把时间都用来详细的复习学过的东西

链式前向星存图的补充

之前的好多模板都是用链前写的,链前不会的话一点都看不懂之前的模板,所以还是重新学习一下

void add(int u,int v,int value) {
	e[cnt].to = v;
	e[cnt].w = value;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}

上述代码就是链前的一个正常的加边操作,感觉链前的存图是以边为单位存的,记录了每条边的性质,包括边权,终点等。代码里u,v,value表示从u到v有边权为value的边
值得注意的是,链前的存图是从前往后存的,但是链前的遍历是从后往前遍历的
e[cnt].to = v; 这一条表示第cnt条边通往v点,注意这里cnt是从0开始的
e[cnt].w = value; 存边权
e[cnt].nxt = head[u] 首先,head[u]表示的是,以u为起点的,最后一条边的编号,nxt表示的是和这条边起点相同的上一条边的编号,为什么这么写是因为你新往后存进去了一条边,那上一个最后一条边就变成倒数第二条边了,在你现在存的这条边的前一条。这个nxt好具有迷惑性,刚开始看以前写的代码的时候就被这个迷惑住了,其实感觉这个叫front会贴切很多……
head[u] = cnt++;这个就是更新以u为起点的最后一条边的编号,代码的含义是先head[u]=cnt,然后cnt++

关于初始化:head数组我们将所有元素初始化为-1,待会遍历的时候会比较方便,cnt的值初始化为0,也就是说我们边的编号是从0开始的

	for(int i = 1;i <= n; i++) {
		printf("%d\n" ,i);
		for(int j = head[i];j != -1; j = e[j].nxt) {
			printf("%d %d %d\n" ,i,e[j].to,e[j].w);
		}
	}

图的遍历如上
我们对于每一个点,都可以通过head找到他以这个点为起点的最后一条边,然后通过e[j].nxt往前遍历,因为我们的每一个nxt都是通过head来赋值的,所以最初的那条边过后,nxt值一定会是最原始的head,也就是-1,这个时候停止就行

dijkstra最短路

复杂度O(mlgn)
最短路算法,通过这个算法,我们可以成功得到所有点到出发点的最短路径
dij的核心想法就是:确定当前的路径是最短的,绕路不可能比他更快
所以dij的核心执行方法就是:扫描所有通路,找到距离出发点最近的路,然后判断我是直达快还是从当前点绕路到那个点更快
需要实现这个操作,我们需要两个数组:
一个是judge[],用来判断当前的点是否已经是最优化结果
一个是dis[],dis[n]记录的是出发点到n点的最短距离,所以我们首先将dis初始化为无穷大,出发点的dis值是0

#include <bits/stdc++.h>
using namespace std;
typedef pair <int,int> pr;
int n,m,s;
priority_queue<pr, vector<pr>,greater<pr> > q; // 优先队列按照first的值排序
struct edge{
	int to,nxt,w;
}e[10000001];
int head[1000001];
int cnt = 0;
bool judge[1000001];
int dis[1000001] = { };
void add(int u,int v,int value) {
	e[cnt].to = v;
	e[cnt].w = value;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}
void dij() {
	q.push(make_pair(0,s));
	while(!q.empty()) {
		pr p = q.top();
		q.pop();
		if(judge[p.second] == true) continue;
		judge[p.second] = true;
		for(int i = head[p.second];i != -1; i = e[i].nxt) {
			if(dis[e[i].to] > dis[p.second] + e[i].w) {
				dis[e[i].to] = dis[p.second] + e[i].w;
				q.push(make_pair(dis[e[i].to],e[i].to));
			}
		}
	}
}
int main () {
	memset(head,-1,sizeof(head));
	scanf("%d %d %d" ,&n,&m,&s);
	for(int i = 1;i <= m; i++) {
		int a,b,c;
		scanf("%d %d %d" ,&a,&b,&c);
		add(a,b,c);
	}
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(judge,false,sizeof(judge));
	dis[s] = 0;
	dij();
	for(int i = 1;i <= n; i++) {
		printf("%d " ,dis[i]);
	}
	return 0;
}

附上实例:

对于这个图,我们从1出发,那么从1我们可以直接到达2,3,6,我们先把2,3,6存入队列,然后距离1直线距离最近的是3,所以我们来看3
3可以到达两个点,5和6,那么我们来看,从3绕路到6是要比从1直接到6快的,所以我们更新dis[6],同理更新dis[5],因为初始化设成了极大值,接下来看5,5可以到达4,8,6,但是6可以通过3更近的到,所以6不动,更新到4和8的距离
接下来看4,4可以到达2,8,通过绕路到4再到2显然是更近的,所以更新2的距离,同理更新8
以此类推,最终可以得到各个点距离起点的最短路

SPFA最短路

这个最短路相对来说比较暴力,根据刚刚dij的想法,我们知道了
如果对于一个点来说,绕路到达下一个点比直接到达下一个点更近,那么我们选择绕路到达下一个点,我们将这个操作叫做松弛
SPFA的基本想法就是,只有一个点在上一轮被松弛成功时,这一轮从这个点连出的点才有可能被成功松弛。
松弛的本质其实是通过一个点中转来缩短距离(如果你看了前置很容易理解)。所以,如果起点到一个点的距离因为某种原因变小了,从起点到这个距离变小的点连出的点的距离也有可能变小(因为可以通过变小的点中转)。
所以SPFA的想法就是反复松弛,把所有上一轮松驰过的点再次放入队列反复松弛,直到队列为空。

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pr;
int m,n;
struct edge{
	int to;
	int nxt;
	int w;
}e[1000001];
bool judge[1000001]; //这个judge数组是判断这个点有没有入队,有的话就不用重复入队了
int head[10000001];
int dis[10000001];
int cnt = 0;
int s;
void add(int u,int v,int value) {
	e[cnt].w = value;
	e[cnt].to = v;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}
queue<int>q;
void spfa() {
	memset(judge,false,sizeof(judge));
	fill(dis+1,dis+n+1,2147483647);
	dis[s] = 0;
	q.push(s);
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		judge[now] = false;
		for(int i = head[now];i != -1; i = e[i].nxt) {
			int v = e[i].to;
			if(dis[v] > dis[now] + e[i].w) {
				dis[v] = dis[now] + e[i].w;
				if(judge[v] == false) {
					q.push(v);
					judge[v] = true;
				}
			}
		}
	}
}

int main () {
	scanf("%d %d %d" ,&n,&m,&s);
	memset(head,-1,sizeof(head));
	for(int i = 1;i <= m; i++) {
		int a,b,c;
		scanf("%d %d %d" ,&a,&b,&c);
		add(a,b,c);
	}
	spfa();
	for(int i = 1;i <= n; i++) {
		printf("%d " ,dis[i]);
	}
	return 0;
}

SPFA的最差复杂度是O(km),k是每个点的入队次数,根据大佬所说,k平均在4左右,但是有些情况下会被卡到接近n,导致了SPFA的效率不如dij稳定

但是SPFA可以解决掉dij无法解决的问题,就是负环。
负环就是在一个环中,所有边权的合为负数,如果这之后你用dij在里面跑的话,由于根据松弛的条件,函数就会一直在负环里跑,你的最短路就会变成-INF,然后出错。而SPFA也是会一直在负环跑,但是我们可以通过判断每一个点的入队次数来判断是不是有负环,如果一个点入队超过n次,就说明他在里头转的次数太多了,这时候就认为有负环

#include <bits/stdc++.h>
using namespace std;
inline int gin() {
    char c = getchar();
    int s = 0, f = 1;
    while (c < '0' || c > '9') {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        s = (s << 3) + (s << 1) + (c ^ 48);
        c = getchar();
    }
    return s * f;
}
const int N = 5e3 + 5;
int n, m, cnt[N], d[N], tot = 0, head[N];
bool h[N], t;
queue<int> q;
struct edge {
    int dis, ne, to;
} e[N << 1]; 
inline void add(int u, int v, int w) {
    e[++tot].dis = w;
    e[tot].to = v;
    e[tot].ne = head[u];
    head[u] = tot;
}
inline void spfa() {
    memset(h, 0, sizeof h);
    memset(cnt, 0, sizeof cnt);
    memset(d, 63, sizeof d);
    h[1] = 1, t = 0, d[1] = 0;
    q.push(1);
    while (q.size()) {
        int u = q.front();
        q.pop();
        h[u] = 0;
        if (cnt[u] == n - 1) {
            t = 1;
            return;
        }
        cnt[u]++;
        for (int i = head[u]; i; i = e[i].ne) {
            int v = e[i].to, w = e[i].dis;
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;
                if (!h[v])
                    h[v] = 1, q.push(v);
            }
        }
    }
}
int main() {
    int T = gin();
    while (T--) {
        n = gin(), m = gin();
        tot = 0;
        memset(head, -1, sizeof head);
        for (int i = 1; i <= m; i++) {
            int u = gin(), v = gin(), w = gin();
            add(u, v, w);
            if (w >= 0)
                add(v, u, w);
        }
        spfa();
        if (t)
            printf("YE5\n");
        else
            printf("N0\n");
    }
    return 0;
}

floyd算法

这个求的是多源最短路,也就是任意x到y的最短路径
使用了非常朴素的dp思想,令dp[i][j]表示从i到j的最短路,那么这条最短路要么是直接从i走到j,要么就是从另一个点k绕路了。所以dp[i][j] = min(dp[i][j],dp[i][k] + dp[k][j])

#include <bits/stdc++.h>
using namespace std;
int a[10001][10001];
int n,m;
int main () {
	scanf("%d %d" ,&n,&m);
	for(int i = 1;i <= n; i++) {
		for(int j = 1;j <= n; j++) {
			if(i == j) a[i][j] = 0;
			else a[i][j] = 0x3f3f3f3f;
		}
	}
	for(int i = 1;i <= m; i++) {
		int x,y,z;
		scanf("%d %d %d" ,&x,&y,&z);
		a[x][y] = min(a[x][y],z);
		a[y][x] = min(a[y][x],z);
	}
	for(int k = 1;k <= n; k++) {
		for(int i = 1;i <= n; i++) {
			for(int j = 1;j <= n; j++) {
				a[i][j] = min(a[i][j],a[i][k] + a[k][j]);
			}
		}
	}
	for(int i = 1;i <= n; i++) {
		for(int j = 1;j <= n; j++) {
			printf("%d " ,a[i][j]);
		}
		printf("\n");
	}
	return 0;
}

最值得注意的就是加边的地方,a[x][y] = min(a[x][y],z);
这样是为了防止有重复的边,比如第一次加了3,第二次样例又加了5,不取最小值直接赋值的话就出问题了

后记 memset

memset可以用来给数组中的元素统一赋值,他的原理是,二进制数可以用8位16进制数来表示,如果用memset赋值一个0x3f,他就会把8位16进制数都设置成3f3f3f3f,进行赋值,因此她不能用来赋值一些奇怪的不规律的数值,例如INT_MAX,这一点一定要注意!

标签:nxt,head,Day9,int,cnt,寒假,judge,集训,dis
From: https://www.cnblogs.com/Crazyman-W/p/17984678

相关文章

  • 寒假生活指导17
    <template><divclass="carbon-quota-page"><!--页面标题--><h1>碳额度查询</h1><!--查询表单区域--><el-formv-if="!loading":model="form"ref="queryForm"la......
  • 大三寒假学习进度笔记15
    今日整理了一下本次项目中使用到的技术 首先数字人方面主要使用到的是辅助神经场(nerf)算法,有关此算法的具体讲解辐射神经场算法——NeRF算法详解-CSDN博客之后是NLP,NLP的全称是NatuarlLanguageProcessing,中文意思是自然语言处理,是人工智能领域的一个重要方向自然语言处理(NL......
  • 寒假生活指导16
    importrequestsurl='http://www.baidu.com'response=requests.get(url=url)#一个类型和六个属性#Response类型print(type(response))#设置响应的编码格式response.encoding='utf-8'#以字符串的形式来返回了网页的源码print(response.text)#返回一......
  • 寒假日记[2024]
    友联MDISL-寒假日记wo2011-寒假日记Won_DER-2024寒假日记shine-2024寒假衍生寒假颓废记前言MD,这学期颓的要死,感觉啥事没干(就算做过啥也忘了),所以我痛定思痛,无论颓也好,学也好,还是记录一下我寒假干的破事吧/fad/fad1.9等等这不是寒假吧(管他的)今天还好,做了些树形D......
  • 寒假集训Day7
    图今天开始了图论,讲了一些基础内容首先是存图存图这里讲的跟当时高中讲的有些区别,高中当时说了一个链式前向星存图现在没讲,不过没关系,反正讲了也不会,先把今天讲的说了一个是非常简单的邻接矩阵存图一个是利用二维vector,每一个vector的行首存初始点,然后一点一点把去向的点往......
  • 集训队互测2023 彩虹航线
    给定一个\(n\)个点\(m\)条边的二分图,每个点的度数都\(\leqslantk\),且每条边的本质不同的备选颜色数目都\(\geqslantk\),求一组边染色,可以证明一定有解。有一个乱搞是每次在加入一条边时按照颜色从小到大,如果当前可以加入则加入,否则如果只会影响一条边则将这条边断掉后再重......
  • 寒假怎么制定学习计划高效?可以给自己制定学习计划的软件
    随着寒冬的降临,寒假也随之而至。对于中小学生和大学生们来说,这是一个放松身心、挖掘兴趣、提升学业的黄金时期。然而,众多学子纷纷表示,寒假在家中往往面临太多诱惑,难以按时完成每天的学习目标。那么如何应对这个问题呢?一款智能的学习计划制定软件或许可以成为解决之道。对于那些......
  • 寒假集训Day6
    Jellyfishandapplehttps://www.luogu.com.cn/problem/CF1875C这道题使用的是贪心首先对于多于m的苹果个数,可以直接分给m个人,所以先把n对m取模,然后考虑剩下的苹果然后考虑不能分的情况如果求出m和n的最大公约数,把m和n同时除以最大公约数,那么得到的人数应该是2k,否则的话就说......
  • 集训队论文浅读 - 信息学竞赛中构造题的常用解题方法
    抽屉原理把\(n\)个物品放入\(k\)个抽屉中,其中至少有一个抽屉中有\(\lceil\dfrac{n}{k}\rceil\)个物品,并一定有一个抽屉包含\(\lfloor\dfrac{n}{k}\rfloor\)个物品。构造题中考虑构造不同情况的抽屉,应对构造权值类问题。对于取整符号要敏感。Codeforces1450C2构......
  • 2024寒假集训 进阶训练赛 (六)部分题解
    A统计单词数题解注意是否是单词。CODECPP#include<iostream>#include<string>#include<algorithm>usingnamespacestd;intmain(){stringword,article;getline(cin,word);getline(cin,article);//转换为小写字母transform(word.beg......