10.12 NOTE

P3008 [USACO11JAN] Roads and Planes G

题目传送门

思路

观察到所有双向道路相连的点一定是强连通的,那么我们可以缩点,将整张图缩成一个 \(DEG\) ,在每一个强连通块内跑 \(Dijkstra\),连通块之间通过拓扑排序更新答案。

总结

Dijkstra

单源最短路问题最基本的解法之一。

核心思想是:每次贪心地选取当前与源点距离最小的点,进行松弛操作。距离最小的点可以通过小根堆维护,时间很优。

贪心的证明:

反证法证明(最严谨)

假设

假设存在某个时刻,我们选择节点u(d[u]最小),但存在一条从s到u的更短路径P,其长度L(P) < d[u]。

分析路径P

路径P:s → ... → x → y → ... → u
其中:
x是路径P上最后一个属于S的节点
y是x在路径P上的下一个节点(y∉S)

推导矛盾

由于x∈S,在x被加入S时,我们已经用x更新过y的距离:
d[y] ≤ d[x] + w(x,y)
路径P从y到u的部分长度非负(边权非负):
L(P) = d[x] + w(x,y) + L(y→u) ≥ d[x] + w(x,y)
结合1和2:
L(P) ≥ d[x] + w(x,y) ≥ d[y]
但u是当前Q中d值最小的节点:
d[u] ≤ d[y]
综合3和4:
L(P) ≥ d[y] ≥ d[u]
这与假设L(P) < d[u]矛盾

结论

假设不成立,因此d[u]确实是s到u的最短距离。

拓扑排序

这一题中根据拓扑序更新了答案,正确性的核心是拓扑排序保证了更新答案无后效性

具体实现

每次将入度为0的结点压入队列依次处理,处理完每个结点后将其出边指向的结点的入度-1。

Code

#include<bits/stdc++.h>
#define Iseri namespace
#define Nina std
#define Kawaragi int
#define Momoka main
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define ll long long
#define ull unsigned long long
#define endl "\n"
#define pii pair<ll,ll>
const int maxn=100005;
const int inf=0x3f3f3f3f;

using Iseri Nina;

inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

//===========================================================

ll def[maxn],in[maxn],d[maxn],n,m,p,s,x,y,w;
bool vis[maxn];
vector<pii >v[maxn],e[maxn];
vector<ll>tmp[maxn];

inline void dfs(ll u,ll num){
	def[u]=num;
	tmp[num].push_back(u);
	for(auto i:v[u]){
		if(!def[i.first])dfs(i.first,num);
	}
}

Kawaragi Momoka(){
	n=read(),m=read(),p=read(),s=read();
	for(ll i=1;i<=m;i++){
		x=read(),y=read(),w=read();
		v[x].emplace_back(y,w);
		v[y].emplace_back(x,w);
	}
	for(ll i=1;i<=p;i++){
		x=read(),y=read(),w=read();
		e[x].emplace_back(y,w);
	}

	ll cnt=0;
	for(ll i=1;i<=n;i++)if(!def[i])dfs(i,++cnt);
	for(ll i=1;i<=n;i++)for(auto j:e[i])in[def[j.first]]++;
	fill(d+1,d+1+n,inf);
	d[s]=0;
	queue<ll>q;
	for(ll i=1;i<=cnt;i++)if(!in[i])q.push(i);

	while(!q.empty()){
		ll a=q.front();q.pop();
		priority_queue<pii,vector<pii>,greater<pii> >pq;
		for(auto i:tmp[a])if(d[i]<inf)pq.push(make_pair(d[i],i));

		while(!pq.empty()){
			pii b=pq.top();pq.pop();
			if(vis[b.second])continue;vis[b.second]=1;

			for(auto i:v[b.second])
				if(d[b.second]+i.second<d[i.first])
					pq.push(make_pair(d[i.first]=d[b.second]+i.second,i.first));
			for(auto i:e[b.second])d[i.first]=min(d[i.first],d[b.second]+i.second);
		}

		for(auto i:tmp[a])
			for(auto j:e[i])if(--in[def[j.first]]==0)q.push(def[j.first]);
	}

	for(ll i=1;i<=n;i++){
		if(d[i]==inf)printf("NO PATH\n");
		else printf("%lld\n",d[i]);
	}
	return 0; 
}

P7771 【模板】欧拉路径

题目传送门

欧拉路径

一笔画问题。就是在图上找一个环,使得每一条边在这个环上出现有且仅有一次

几个性质:

有向图欧拉路径:图中恰好存在 1 个点出度比入度多 1(这个点即为 起点 S),1 个点入度比出度多 1(这个点即为 终点 T),其余节点出度=入度。

有向图欧拉回路:所有点的入度=出度(起点 S 和终点 T 可以为任意点)。

无向图欧拉路径:图中恰好存在 2 个点的度数是奇数,其余节点的度数为偶数,这两个度数为奇数的点即为欧拉路径的 起点 S 和 终点 T。

无向图欧拉回路:所有点的度数都是偶数(起点 S 和终点 T 可以为任意点)。

注:存在欧拉回路(即满足存在欧拉回路的条件),也一定存在欧拉路径。

求法

总之先记录每个点的入度和出度,在检查是否存在欧拉路径后,\(dfs\) 将所有经过的点压入栈中,最后一个个弹出输出即可。但要注意,在经过一条边之后要删掉这条边,避免重复经过。
删边可以用一个 map 或者 vis 数组存储是否访问过,随后正常 DFS 即可。

Code

#include<bits/stdc++.h>
#define Iseri namespace
#define Nina std
#define Kawaragi int
#define Momoka main
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define ll long long
#define ull unsigned long long
#define endl "\n"
#define pii pair<ll,ll>
const int maxn=100005;
const int inf=0x3f3f3f3f;

using Iseri Nina;

inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

//===========================================================

ll n,m,x,y,cd[maxn],rd[maxn],p[maxn],s=1,cnt[2];
vector<ll>v[maxn];
stack<ll>st;

inline void dfs(ll u){
	for(ll i=p[u];i<v[u].size();i=p[u]){
		p[u]=i+1;//这里这个p是记录了对于这个点应该走哪一条边(前面的边已经走过了)
		dfs(v[u][i]);
	}
	st.push(u);
}

Kawaragi Momoka(){
	n=read(),m=read();
	for(ll i=1;i<=m;i++){
		x=read(),y=read();
		v[x].push_back(y);
		cd[x]++;rd[y]++;
	}
	for(ll i=1;i<=n;i++)sort(v[i].begin(),v[i].end());

	bool flag=1;
	for(ll i=1;i<=n;i++){
		if(rd[i]!=cd[i]){
			flag=0;
			if(cd[i]-rd[i]==1)cnt[1]++,s=i;
			else if(rd[i]-cd[i]==1)cnt[0]++;
			else{printf("No");return 0;}
		}
	}
	if((!flag)&&!(cnt[0]==cnt[1]&&cnt[0]==1)){printf("No");return 0;}

	dfs(s);
	while(!st.empty()){printf("%lld ",st.top());st.pop();}
	return 0; 
}

CSDN好文章

posted @ 2025-11-13 23:55  Amiyawasdonkey  阅读(0)  评论(0)    收藏  举报