图论

P4281 [AHOI2008] 紧急集合 / 聚会

当点 \(x\) 在 \(y,z\) 上时,答案即为 \(x\)。

不然情况如下图:

::: fold

      x
      |
      |
      p
     / \
    y   z

:::

答案为 \(p\)。

所以答案为三个点两两的 \(LCA\) 中,深度较大的 \(LCA\)。

距离预处理树上差分。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
vector<int>v[500005];
int d[500005],fa[500005][20];
void dfs(int x,int f){
	fa[x][0]=f;
	for(int i=0;i<v[x].size();i++){
		if(v[x][i]==f) continue;
		d[v[x][i]]=d[x]+1;
		dfs(v[x][i],x);
	}
}
void init(){
	for(int k=1;k<=19;k++){
		for(int i=1;i<=n;i++){
			if(fa[i][k-1]!=0&&fa[fa[i][k-1]][k-1]!=0){
				fa[i][k]=fa[fa[i][k-1]][k-1];
			}
		}
	}
}
int lca(int x,int y){
	if(d[x]<d[y]) swap(x,y);
	for(int i=19;i>=0;i--){
		if(d[fa[x][i]]>=d[y]) x=fa[x][i];
	}
	for(int i=19;i>=0;i--){
		if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	}
	if(x==y) return x;
	return fa[x][0];
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n-1;i++){
		int u,vv;
		cin>>u>>vv;
		v[u].push_back(vv);
		v[vv].push_back(u);
	}
    d[1]=1;
	dfs(1,0);
	init();
	while(m--){
		int x,y,z;
		cin>>x>>y>>z;
		int p=0;
		if(d[lca(x,y)]>=d[p]) p=lca(x,y);
		if(d[lca(x,z)]>=d[p]) p=lca(x,z);
		if(d[lca(y,z)]>=d[p]) p=lca(y,z);
		int ans=0;
		ans+=d[x]+d[p]-2*d[lca(x,p)];
		ans+=d[y]+d[p]-2*d[lca(y,p)];
		ans+=d[z]+d[p]-2*d[lca(z,p)];
		cout<<p<<" "<<ans<<endl;
	}
	return 0;
}

P2971 [USACO10HOL] Cow Politics G

考虑分党派处理。

对于某个党派,先取其中前两个节点 \(a,b\),把它们作为当前点集直径的两个端点。

之后依次加入新的节点 \(x\)。

假设加入 \(x\) 之前,当前所有节点的直径端点为 \(a,b\),那么加入 \(x\) 后,新的直径只可能是以下三条路径中的最长者:

\[dis(a,b),dis(a,x),dis(b,x) \]

因为原来的所有节点之间的最大距离已经是 \(dis(a,b)\),加入一个新点以后,如果直径发生改变,那么新的直径一定包含新加入的节点 \(x\)。

这样不断加入该党派的所有节点,最后得到的 \(a,b\) 就是这个党派的直径端点,答案就是 \(dis(a,b)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
vector<int>v[200005];
int d[200005],fa[200005][20];
int h[200005];
vector<int>g[200005];
void dfs(int x,int f){
	fa[x][0]=f;
	for(int i=0;i<v[x].size();i++){
		if(v[x][i]==f) continue;
		d[v[x][i]]=d[x]+1;
		dfs(v[x][i],x);
	}
}
void init(){
	for(int k=1;k<=19;k++){
		for(int i=1;i<=n;i++){
			if(fa[i][k-1]!=0&&fa[fa[i][k-1]][k-1]!=0){
				fa[i][k]=fa[fa[i][k-1]][k-1];
			}
		}
	}
}
int lca(int x,int y){
	if(d[x]<d[y]) swap(x,y);
	for(int i=19;i>=0;i--){
		if(d[fa[x][i]]>=d[y]) x=fa[x][i];
	}
	for(int i=19;i>=0;i--){
		if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	}
	if(x==y) return x;
	return fa[x][0];
}
signed main(){
	cin>>n>>k;
	int gg=0;
	for(int i=1;i<=n;i++){
		int x,y;
		cin>>x>>y;
		if(y==0) gg=i;
		h[i]=x;
		g[x].push_back(i);
		v[y].push_back(i);
		v[i].push_back(y);
	}
	d[gg]=1;
	dfs(gg,0);
	init();
	for(int i=1;i<=k;i++){
		int a=g[i][0],b=g[i][1];
		for(int j=2;j<g[i].size();j++){
			int f1=d[g[i][j]]+d[a]-2*d[lca(g[i][j],a)],f2=d[g[i][j]]+d[b]-2*d[lca(g[i][j],b)];
			if(max({f1,f2,d[a]+d[b]-2*d[lca(a,b)]})==f1) b=g[i][j];
			else if(max({f1,f2,d[a]+d[b]-2*d[lca(a,b)]})==f2) a=g[i][j];
		}
		cout<<d[a]+d[b]-2*d[lca(a,b)]<<endl;
	}
	return 0;
}

P6190 [NOI Online #1 入门组] 魔法

先把路径问题转化成 \(\min,+\) 矩阵乘法。定义矩阵乘法为

\[(A\times B)_{i,j}=\min_k(A_{i,k}+B_{k,j}) \]

这样矩阵乘法就表示“把两段路径拼起来”。

设 \(I\) 为单位矩阵:

\[I_{i,i}=0,\qquad I_{i,j}=+\infty\ (i\ne j) \]

再构造两个矩阵:

  • \(I_2\):普通道路矩阵,其中存在边 \(u\to v\)、权值为 \(w\) 时,\(I_{2,u,v}=w\),并且 \(I_{2,i,i}=0\);
  • \(I_3\):魔法矩阵,其中存在边 \(u\to v\)、权值为 \(w\) 时,\(I_{3,u,v}=-w\),同时 \(I_{3,i,i}=0\)。

这里 \(I_3\) 的对角线为 \(0\),相当于:

\[I_3=I\oplus M \]

即这一轮可以选择使用一次魔法,也可以选择不使用魔法。

由于普通边权全部为正,普通最短路一定可以取成简单路径,因此最多经过 \(n-1\) 条边。于是从单位矩阵开始连续乘 \(n-1\) 次 \(I_2\):

\[D=I\times I_2^{\,n-1} \]

就能得到普通道路的全源最短路矩阵 \(D\)。

接下来,一轮“普通走任意距离,然后可以使用一次魔法”的转移矩阵为

\[P=D\times I_3 \]

也就是

\[P=D(I\oplus M) \]

如果进行 \(k\) 轮,得到

\[P^k=[D(I\oplus M)]^k \]

由于 \(\min,+\) 矩阵乘法满足结合律,所以 \(P^k\) 可以使用矩阵快速幂,在 \(O(\log k)\) 次矩阵乘法内求出。

最后一次魔法之后,还可能继续沿普通道路走到终点,所以最后还需要乘一次普通最短路矩阵 \(D\)。

因此最终要求的矩阵为

\[[D(I\oplus M)]^kD \]

答案就是

\[([D(I\oplus M)]^kD)_{1,n} \]

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,kk;
int l[105][105],l2[105][105],l3[105][105],p[105][105];
int ll[105][105];
void qpow(int a){
	while(a){
		if(a&1){
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					if(i!=j) ll[i][j]=1e18;
					else ll[i][j]=0;
				}
			}
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					for(int k=1;k<=n;k++){
						if(l[i][k]==1e18||p[k][j]==1e18) continue;
						ll[i][j]=min(ll[i][j],l[i][k]+p[k][j]);
					}
				}
			}
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					l[i][j]=ll[i][j];
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i!=j) ll[i][j]=1e18;
				else ll[i][j]=0;
			}
		}		
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				for(int k=1;k<=n;k++){
					if(p[i][k]==1e18||p[k][j]==1e18) continue;
					ll[i][j]=min(ll[i][j],p[i][k]+p[k][j]);
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				p[i][j]=ll[i][j];
			}
		}
		a>>=1;
	}
}
signed main(){
	cin>>n>>m>>kk;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i!=j) l2[i][j]=1e18;
			if(i!=j) l3[i][j]=1e18;
		}
	} 
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		l2[u][v]=w;
		l3[u][v]=-w;
	} 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i!=j) l[i][j]=1e18,p[i][j]=1e18;
		}
	}
	for(int p1=1;p1<=n-1;p1++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i!=j) ll[i][j]=1e18;
				else ll[i][j]=0;
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				for(int k=1;k<=n;k++){
					if(p[i][k]==1e18||l2[k][j]==1e18) continue;
					ll[i][j]=min(ll[i][j],p[i][k]+l2[k][j]);
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				p[i][j]=ll[i][j];
			}
		}
	}		
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i!=j) ll[i][j]=1e18;
			else ll[i][j]=0;
		}
	}		
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			for(int k=1;k<=n;k++){
				if(p[i][k]==1e18||l3[k][j]==1e18) continue;
				ll[i][j]=min(ll[i][j],p[i][k]+l3[k][j]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			p[i][j]=ll[i][j];
		}
	}	
	qpow(kk);
	for(int p1=1;p1<=n-1;p1++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i!=j) ll[i][j]=1e18;
				else ll[i][j]=0;
			}
		}		
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				for(int k=1;k<=n;k++){
					if(l[i][k]==1e18||l2[k][j]==1e18) continue;
					ll[i][j]=min(ll[i][j],l[i][k]+l2[k][j]);
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				l[i][j]=ll[i][j];
			}
		}
	}	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			l[i][j]=ll[i][j];
		}
	}
	cout<<l[1][n];
	return 0;
}

P1266 [BalticOI 2002] 速度限制

把当前位置和当前速度一起作为最短路状态。

因为遇到未知限速的道路时,行驶时间取决于之前保留的速度,所以不能只记录到某个点的最短时间。

转移时:

  • 若道路限速未知,则保持原速度;
  • 若道路有明确限速,则当前速度变为该限速。

在这些状态上跑 Dijkstra。每次某个状态被成功松弛时,记录它是从哪个前驱节点和前驱速度转移过来的。

最后枚举所有到达终点的速度状态,选出总时间最短的那个状态,再沿记录的前驱状态一路倒推回初始状态 \((0,70)\),即可还原出整条最优路径。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,d;
struct edge{
	int vv,vt;
	double l;
};
vector<edge>v[155];
bool operator < (edge x,edge y){
	return x.l>y.l;
}
priority_queue<edge>q;
double dp[155][505];
bool vis[155][505];
struct las{
	int vv,vt;
};
las lasp[155][505];
void dijkstra(){
	for(int i=0;i<=n-1;i++){
		for(int j=0;j<=500;j++){
			dp[i][j]=1e18;
		}
	}
	dp[0][70]=0;
	q.push({0,70,0});
	while(!q.empty()){
		int p=q.top().vv;
		int vt=q.top().vt;
		q.pop();
		if(vis[p][vt]) continue;
		vis[p][vt]=1;
		for(int i=0;i<v[p].size();i++){
			if(v[p][i].vt==0){
				if(dp[v[p][i].vv][vt]>dp[p][vt]+(v[p][i].l*1.0/vt)){
					dp[v[p][i].vv][vt]=dp[p][vt]+(v[p][i].l*1.0/vt);
					lasp[v[p][i].vv][vt]={p,vt};
					q.push({v[p][i].vv,vt,dp[v[p][i].vv][vt]});
				}
			}
			else{
				int vp=v[p][i].vt;
				if(dp[v[p][i].vv][vp]>dp[p][vt]+(v[p][i].l*1.0/vp)){
					dp[v[p][i].vv][vp]=dp[p][vt]+(v[p][i].l*1.0/vp);
					lasp[v[p][i].vv][vp]={p,vt};
					q.push({v[p][i].vv,vp,dp[v[p][i].vv][vp]});
				}				
			}
		}
	}
}
signed main(){
	cin>>n>>m>>d;
	for(int i=1;i<=m;i++){
		int u,vv,vt,l;
		cin>>u>>vv>>vt>>l;
		v[u].push_back({vv,vt,l});
	}
	dijkstra();
	double mh=1e18;
	int r1=d,r2=-1;
	for(int i=0;i<=500;i++){
		if(dp[d][i]<mh) mh=dp[d][i],r2=i;
	}
	stack<int>st;
	while(r1!=0){
		st.push(r1);
		int p1=lasp[r1][r2].vv,p2=lasp[r1][r2].vt;
		r1=p1,r2=p2;
	}
	st.push(0);
	while(!st.empty()) cout<<st.top()<<" ",st.pop();
	return 0;
}
posted @ 2026-10-07 16:00  easy42  阅读(13)  评论(0)    收藏  举报