[洛谷P15426 Time to Heal题解]机房小南梁写完就似了

这啥玩意,tag 认真的吗。理清学习成果顺便写一篇题解。

题意简述

给定 \(n\) 个点 \(m\) 条边的简单无向图,不保证连通,\(q\) 次查询,每次给出 \(S,T,k\),在 \(S\) 点到 \(T\) 点的所有可以重复走边走点的路径中选择两条(可以选两个一样的),定义 \(k\)-TS 为这两条路径的满足长度为 \(k\) 的极长公共子序列,你需要求出字典序最小的那个 \(k\)-TS,输出点编号之和。

思路

首先判掉 \(S\)\(T\) 不连通的情况。

考虑 \(k\)-TS 的性质:设一条合法的 \(k\)-TS \(\{v_i\}_{i=1}^{k}\),其中 \(v_1=S\)\(v_k=T\)

选择的两条路径在 \(v_i\)\(v_{i+1}\) 再无交点,否则违背定义。则你一定轻松看出来 \(v_i\)\(v_{i+1}\) 在同一个点双里。

但别忘了有可能 \(v_i=v_{i+1}\),这就需要 \(v_i\) 至少连着两个不同的点。

定义 TS 为 \(k\) 为任意值的 \(k\)-TS,显然两点之间的割点一定在 TS 中,那么我们就可以得出合法 TS 的长度下界: S、T 之间割点个数 \(+2\),如果 \(k\) 小于这个值直接判掉。对于快速查询,通过建立广义圆方树不难做到。

下面考虑如何从小于等于 \(k\) 凑到恰好为 \(k\)

我们需要一个方法判定对于给定的起点 \(u\) 和终点 \(v\) 是否有解,显然如果差的是偶数很容易凑够,只需要来回走就可以。而如果差的是奇数,则需要想办法凑出来个 \(1\)。不难发现结论:

  • 两不同的点 \(u,v\) 同属于一个大小至少为 \(3\) 的点双连通分量,则该点双任意点均可以任意顺序插入 TS。

    设第三个点为 \(x\),根据点双定义,必然有中间不交的两条路径 \(P_1,P_2\) 连接 \(u,x\),同理也有中间不交的两条路 \(Q_1,Q_2\) 连接 \(x,v\)。可能存在 \(y\neq x,y\in P_1\cap Q_2\),但公共子序列关心相对顺序,第一条路径依次经过 u-y-x-v,而第二条依次经过 u-x-y-v,则 \(x,y\) 必然只有一个能出现在同一个 TS 里。

这样一般情况下必然能凑出来 \(1\)。但如果 \(u=v\)\(u\) 只连了一条边,就只能往走这条边。假设这条边连到 \(x\),而 \(x\) 的度数也是 \(1\)(也就是连通块大小为 \(2\)),显然两路径怎么走都重合,只能凑出偶数。否则一条的路径为 u-x-u-x-u,另一条为 u-x-y-x-u,贡献三步。

这样我们就有了指定奇偶性下求解最短路长度的方案,只要来回走凑够偶数就是有解判定。

需要令字典序最小。

根据前面的结论,可以贪心的选当前点连着的编号最小点,令高位尽可能小。容易查询当前是否存在合法方案,所以在时刻合法的前提下从 \(S\) 出发无脑走最小编号的点即可,这一步可以倍增优化。

贪心结束过后,设最短路为 \(d\),剩下 \(k'\) 步。

  • 贪心结束后,剩余步数 \(k'\) 一定恰好等于当前奇偶性下的最短合法距离。

    假设 \(u\) 是合法的,钦定奇偶性的最短路为 \(D\)\(k'-D\geq 2\),它再跳一步到 \(v\)\(v\) 至少可以花费一步跳回 \(u\),最短路上界为 \(D+1\),而剩余步数只花了一步,\(k'-1\geq D+1\),且 \(k'-D\) 奇偶性不变,必然合法。

根据我们对最短路的分析,\(k'\) 是被钦定奇偶性下的最短路,那么一般情况下有 \(d\leq k'\leq d+1\)。但别忘了上面说过有特例,如果贪心结束后刚好跑到 \(T\),该点度数为 \(1\)\(k'=3\) ,则需要特判掉。

\(k'=d\) 直接结束了,对于 \(d=k'-1\),需要想办法再塞一个。

还是那个塞点逻辑,由于是字典序,从前往后找是否存在相邻两点 \(u,v\) 存在办法让中间塞一个比 \(v\) 小的,可以在点双里找个最小的,如果 \(u\) 满足度数至少为 \(2\) 也可以选 \(u\),比较是否更优。预处理可以做到快速查询。

如果不存在能让下一项严格变小的位置,就把额外的一步尽量向后拖。如果可以多塞一个 \(T\) 就塞,否则由于我们已经判掉连通块大小为 \(2\) 的情况,它唯一连接的点一定可以多塞一个点。

这样这个题就结束了。

AC code

#include <bits/stdc++.h>
#define int int64_t
//#define int __int128
//#define MOD (1000000007)
//#define eps (1e-6)
#define endl '\n'
#define debug_endl cout<<endl;
#define debug cout<<"debug"<<endl;
using namespace std;
const int MAXN=5e5+10;
struct DSU{
	int fa[MAXN],siz[MAXN];
	inline int find(int x){
		return (fa[x]==x?x:fa[x]=find(fa[x]));
	}
	inline void merge(int x,int y){
		int fx=find(x),fy=find(y);
		if(fx!=fy){
			fa[fx]=fy;
			siz[fy]+=siz[fx];
		}
	}
};
DSU dsu;
int c,n,m,q;
vector<int> g[MAXN];
vector<int> G[MAXN<<1];
int dfn[MAXN],low[MAXN],idx,t;
int st[MAXN],top,BCC;
int dep[MAXN<<1],fa[24][MAXN<<1],nex[32][MAXN<<1],sec_nex[MAXN<<1],val[MAXN<<1],sum[32][MAXN];
int st_lca[24][MAXN<<2],tot,fir[MAXN<<1],lg2[MAXN<<2];
int up[MAXN<<1],down[MAXN<<1];
bool vised[MAXN<<1];
void tarjan(int x,int fa){
	dfn[x]=low[x]=++idx;
	st[++top]=x;
	for(int v:g[x]){
		if(v!=fa){
			if(!dfn[v]){
				tarjan(v,x);
				low[x]=min(low[x],low[v]);
				if(low[v]>=dfn[x]){
					++BCC;
					sec_nex[BCC]=INT64_MAX;
					nex[0][BCC]=INT64_MAX;
					while(st[top]!=v){
						G[BCC].emplace_back(st[top]);
						G[st[top]].emplace_back(BCC);
						sec_nex[BCC]=min(sec_nex[BCC],st[top]);
						if(sec_nex[BCC]<nex[0][BCC]) swap(nex[0][BCC],sec_nex[BCC]);
						--top;
					}
					G[BCC].emplace_back(x);
					G[x].emplace_back(BCC);
					G[BCC].emplace_back(v);
					G[v].emplace_back(BCC);
					sec_nex[BCC]=min(sec_nex[BCC],x);
					if(sec_nex[BCC]<nex[0][BCC]) swap(nex[0][BCC],sec_nex[BCC]);
					sec_nex[BCC]=min(sec_nex[BCC],v);
					if(sec_nex[BCC]<nex[0][BCC]) swap(nex[0][BCC],sec_nex[BCC]);
					--top;
				}
			}
			else low[x]=min(low[x],dfn[v]);
		}
	}
}
inline int tryinsert(int x,int y){
	int ch=(y==fa[1][x]?x:y);
	if(g[x].size()>=2){
		return nex[0][fa[0][ch]];
	}
	else{
		int tmp=nex[0][fa[0][ch]];
		if(tmp==x) tmp=sec_nex[fa[0][ch]];
		return tmp;
	}
}
void dfs(int x,int f){
	fir[x]=++tot;
	st_lca[0][tot]=x;
	fa[0][x]=f;
	vised[x]=true;
	dep[x]=dep[f]+1;
	for(int i=1;i<=t;++i){
		fa[i][x]=fa[i-1][fa[i-1][x]];
	}
	up[x]=up[f];
	down[x]=down[f];
	val[x]=val[f];
	if(x<=n){
		if(fa[1][x]&&tryinsert(x,fa[1][x])<fa[1][x]) up[x]=x;
		if(fa[1][x]&&tryinsert(fa[1][x],x)<x) down[x]=x;
		val[x]+=x;
	}
	for(int v:G[x]){
		if(v!=f){
			dfs(v,x);
			st_lca[0][++tot]=x;
		}
	}
}
inline int getLCA(int x,int y){
	int l=min(fir[x],fir[y]),r=max(fir[y],fir[x]);
	int o=lg2[r-l+1];
	return dep[st_lca[o][l]]<dep[st_lca[o][r-(1ll<<o)+1]]?st_lca[o][l]:st_lca[o][r-(1ll<<o)+1];
}
inline int getDis(int x,int y){
	int l=getLCA(x,y);
	return (dep[x]+dep[y]-(dep[l]<<1))>>1;
}
inline int getDis2(int x,int y){
	int l=getLCA(x,y);
	return (val[x]+val[y]-val[l]-val[fa[0][l]]);
}
inline int getDis_s(int x,int y,int o){
	int d=getDis(x,y);
	if((d-o)&1){
		if(x==y){
			if(g[x].size()>=2) return 1;
			else return 3;
		}
		return d+1;
	}
	else return d;
}
inline int getK(int x,int k){
	for(int i=t;i>=0;--i){
		if(k&(1ll<<i)) x=fa[i][x];
	}
	return x;
}
inline int solve(int S,int T,int k){
	if(dsu.find(S)!=dsu.find(T)) return -1;
	if(dsu.siz[dsu.find(S)]==2) return (k&1?-1:(k>>1)*(S+T));
	--k;
	if(getDis_s(S,T,k&1)>k) return -1;
	int ans=S;
	for(int i=31;i>=0;--i){
		int nk=k-(1ll<<i);
		if(nk>=0&&getDis_s(nex[i][S],T,nk&1)<=nk){
			ans+=sum[i][S];
			S=nex[i][S];
			k=nk;
		}
	}
	if(S==T&&k==3){
		return ans+nex[0][S]*2+T;
	}
	ans+=getDis2(S,T)-S;//直接走最短路,S算了两次
	if(k==getDis(S,T)) return ans;
	int lca=getLCA(S,T);
	if(dep[up[S]]-2>=dep[lca]) return ans+tryinsert(up[S],fa[1][up[S]]);
	if(lca>n){
		int u=getK(S,dep[S]-dep[lca]-1);
		int v=getK(T,dep[T]-dep[lca]-1);
		int tmp=tryinsert(u,v);
		if(tmp<v) return ans+tmp;
	}
	if(dep[down[T]]-2>=dep[lca]){
		for(int i=t;i>=0;--i) {
			if(dep[down[fa[i][T]]]-2>=dep[lca]){
				T=fa[i][T];
			}
		}
		return ans+tryinsert(fa[1][down[T]],down[T]);
	}
	if(g[T].size()>=2) return ans+T;
	return ans+nex[0][T];
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>c;
	cin>>n>>m;
	lg2[0]=-1;
	for(int i=1;i<=n*4;++i) lg2[i]=lg2[i>>1]+1;
	t=lg2[n]+2;
	BCC=n;
	for(int i=1;i<=n;++i) dsu.fa[i]=i,dsu.siz[i]=1;
	for(int i=1;i<=m;++i){
		int u,v;
		cin>>u>>v;
		g[u].emplace_back(v);
		g[v].emplace_back(u);
		dsu.merge(u,v);
	}
	for(int i=1;i<=n;++i){
		if(!dfn[i]){
			tarjan(i,0);
			--top;
		}
	}
	for(int i=1;i<=n;++i){
		nex[0][i]=INT64_MAX;
		if(g[i].size()>=2) nex[0][i]=i;//度数大于等于2下一个可以走自己
		for(int rt:G[i]){
			nex[0][i]=min(nex[0][i],(nex[0][rt]==i?sec_nex[rt]:nex[0][rt]));
		}
		sum[0][i]=(nex[0][i]==INT64_MAX?0:nex[0][i]);
	}
	for(int j=1;j<=31;++j){
		for(int i=1;i<=n;++i){
			if(nex[j-1][i]!=INT64_MAX){
				nex[j][i]=nex[j-1][nex[j-1][i]];
				sum[j][i]=sum[j-1][i]+sum[j-1][nex[j-1][i]];
			}
		}
	}
	for(int i=1;i<=BCC;++i){
		if(!vised[i]){
			dfs(i,0);
		}
	}
	for(int j=1;j<=t+1;++j){
		for(int i=1;i<=(tot-(1<<j)+1);++i){
			int l=dep[st_lca[j-1][i]],r=dep[st_lca[j-1][i+(1ll<<(j-1))]];
			st_lca[j][i]=(l<r?st_lca[j-1][i]:st_lca[j-1][i+(1ll<<(j-1))]);
		}
	}
	cin>>q;
	for(int i=1;i<=q;++i){
		int S,T,k;
		cin>>S>>T>>k;
		cout<<solve(S,T,k)<<endl;
	}
	return 0;
}
posted @ 2026-08-26 21:07  司马只因锥  阅读(6)  评论(0)    收藏  举报