需要记忆的算法

次小生成树

先生成最小生成树,再预处理出两点之间的最大边权,枚举非树边,替换两点之中的最大边,得到最小的 s u m + w − d i s t [ a ] [ b ] sum+w-dist[a][b] sum+wdist[a][b]
非严格的次小生成树,可以只记录最大边,要生成严格的次小生成树,需要记录最大边和次大边,防止两点之间最大边权等于非树边导致无法替换的情况。

//严格次小生成树
#include 
using namespace std;
const int N = 5 * 1e2 + 5,M = 1e4 + 5;
typedef long long LL;
int n,m;
struct Edge{
	int a,b,w;
	bool f;
	int next;
	bool operator< (const Edge &t) const{
		return w < t.w;
	}
}edge[M],e[M];
int head[N],dist1[N][N],dist2[N][N];
int p[N];
int idx;
int find(int x){
	if(x == p[x]) return x;
	else return p[x] = find(p[x]);
}
void ad(int fr,int to,int dis){
	idx ++;
	e[idx].a = fr;
	e[idx].b = to;
	e[idx].w = dis;
	e[idx].next = head[fr];
	head[fr] = idx;
}
void dfs(int u,int fa,int dmax1,int dmax2,int d1[],int d2[]){
	d1[u] = dmax1;
	d2[u] = dmax2;
	for(int i = head[u];i;i = e[i].next){
		if(e[i].b != fa){
			int td1 = dmax1,td2 = dmax2;
			if(e[i].w > td1) td2 = td1,td1 = e[i].w;
			else if(e[i].w > td2 && e[i].w < td1) td2 = e[i].w;
			dfs(e[i].b,u,td1,td2,d1,d2);
		}
	}
}
int main(){
	cin >> n >> m;
	for(int i = 1;i <= m;i ++){
		int fr,to,dis;
		scanf("%d%d%d",&fr,&to,&dis);
		edge[i] = {fr,to,dis};
	}
	LL sum = 0;
	sort(edge + 1,edge + m + 1);
	for(int i = 1;i <= n;i ++) p[i] = i;
	for(int i = 1;i <= m;i ++){
		int fr = edge[i].a;
		int to = edge[i].b;
		if(find(fr) != find(to)){
			int a = find(fr);
			int b = find(to);
			p[a] = b;
			sum += edge[i].w;
			edge[i].f = true;
			ad(edge[i].a,edge[i].b,edge[i].w);
			ad(edge[i].b,edge[i].a,edge[i].w);
		}
	}
	for(int i = 1;i <= n;i ++) dfs(i,-1,-1e9,-1e9,dist1[i],dist2[i]);
	LL res = 1e18;
	//cout << sum;
	for(int i = 1;i <= m;i ++){
		if(! edge[i].f){
			int fr = edge[i].a;
			int to = edge[i].b;
		//	cout << dist1[fr][to] << " " << dist2[fr][to] << endl;
			int dis = edge[i].w;
			if(dis > dist1[fr][to]) res = min(res,sum - dist1[fr][to] + dis);
			else if(dis > dist2[fr][to]) res = min(res,sum - dist2[fr][to] + dis);
		}
	}
	cout << res;
	return 0;
}

负环

基础判断负环,看讲解觉得特别基础的东西,但是写起来也遇到一些问题。
1.注意数组清空。
2.不用判断距离,假设存在负环,那么只需要从负边开始走就能存在负环,所以刚开始将所有点入队,不用每个点分别开SPFA。
3.在一个图都是正边的情况下,一个点到原点的距离不可能超过n,如果超过n,就说明存在负环,可以用边思考。

#include 
using namespace std;
const int N = 510,M = 5500;
int n,m,w;
int idx;
struct Edge{
	int fr,to,dis,next;
}e[M];
int head[N],cnt[N],dist[N];
bool st[N];
void ad(int fr,int to,int dis){
	idx ++;
	e[idx].fr = fr;
	e[idx].to = to;
	e[idx].dis = dis;
	e[idx].next = head[fr];
	head[fr] = idx;
}
bool SPFA(){
	memset(cnt,0,sizeof cnt);
	memset(dist,0,sizeof dist);
	memset(st,true,sizeof st);
	queue <int> q;
	for(int i = 1;i <= n;i ++)
		q.push(i);
	while(! q.empty()){
		int fir = q.front();
		q.pop();
		st[fir] = false;
		for(int i = head[fir];i;i = e[i].next){
			if(dist[fir] + e[i].dis < dist[e[i].to]){
				cnt[e[i].to] = cnt[fir] + 1;
				if(cnt[e[i].to] >= n) return true;
				dist[e[i].to] = dist[fir] + e[i].dis;
				if(! st[e[i].to]){
					q.push(e[i].to);
					st[e[i].to] = true;
				}
			}
		}
	}
	return false;
}
int main(){
	int t;
	cin >> t;
	while(t --){
		cin >> n >> m >> w;
		idx = 0;
		memset(head,0,sizeof head);
		for(int i = 1;i <= m;i ++){
			int fr,to,dis;
			cin >> fr >> to >> dis;
			ad(fr,to,dis);
			ad(to,fr,dis);
		}
		for(int i = 1;i <= w;i ++){
			int fr,to,dis;
			cin >> fr >> to >> dis;
			ad(fr,to,-dis);
		}
		if(SPFA()) puts("YES");
		else puts("NO");
	}
	return 0;
}

正环用最长路,负环用最短路。

你可能感兴趣的:(大一算法学习,c++,算法,数据结构)