提高组热身赛小计(非题目顺序)

T1商务旅行

题意

给定一棵 N 个结点的树,所有边的权值均为 1。从结点 1 出发,依次经过 M 个指定结点,求路径总长度的最小值。

思路

代码首先通过深度优先搜索(DFS)预处理出每个城镇的深度以及祖先信息(用于快速计算最近公共祖先),然后利用最近公共祖先(LCA)算法计算商人按给定顺序经过各个城镇时的最短路径总时间。
计算过程中,每两个连续城镇之间的最短路径长度通过它们的深度与它们 LCA 的深度计算得出(公式为:d[x] + d[y] - 2 * d[LCA(x,y)]),最后将所有连续城镇间的路径长度累加,得到总的旅行时间。
这一实现适用于城镇以树结构连接的场景(无环,任意两城镇间有唯一路径),能高效处理较大规模的输入数据(城镇数达$ 3×10⁴$,经过的城镇数达$ 3×10⁵$)。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=30005;
int h[N],fa[N][25];
vector<int>G[N];
void dfs(int x,int f) {
	h[x]=h[f]+1;
	fa[x][0]=f;
	for(int i=1; i<=20; ++i){
		fa[x][i]=fa[fa[x][i-1]][i-1];
	}
	for(int y:G[x]) {
		if(y!=f)dfs(y,x);
	}
}
int LCA(int x,int y) {//倍增 
	if(x==y)return x;
	if(h[x]>h[y])swap(x,y);
	for(int i=20; i>=0; --i) {
		if(h[fa[y][i]]>=h[x]) {
			y=fa[y][i];
		}
	}
	if(x==y)return x;
	for(int i=20; i>=0; --i)
		if(fa[x][i]!=fa[y][i]) {
			x=fa[x][i];
			y=fa[y][i];
		}
	return fa[x][0];
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n;
	cin>>n;
	for(int i=1; i<n; ++i) {
		int a,b;
		cin>>a>>b;
		G[a].push_back(b);
		G[b].push_back(a);
	}
	dfs(1,0);
	int m;
	cin>>m;
	long long res=0;
	int rs;
	cin>>rs;
	for(int i=1; i<m; ++i) {
		int mw;
		cin>>mw;
		int ac=LCA(rs,mw);
		res+=h[rs]+h[mw]-2*h[ac];
		rs=mw;
	}
	cout<<res<<"\n";
	return 0;
}

T2无线传输

题意

给你一个字符串s1,它是由某个字符串s2不断自我连接形成的(保证至少重复 2 次)。但是字符串s2是不确定的,现在只想知道它的最短长度是多少。

思路

||经典的KMP算法q(≧▽≦q)推导发现:
设循环节的长度为x,那么kmp数组前x个都是0,后面kmp[x+n]=n
先求出kmp数组
答案实际就是len-kmp[len]

代码copy

#include <bits/stdc++.h>
#define N 1000111
using namespace std;
string a;
int k[N];
int main() {
	int n;
	cin>>n>>a;
	k[0]=0;
	k[1]=0;
	for(int i=1,ks=0;i<n;++i) {
		while(ks!=0&&a[i]!=a[ks]) {
			ks=k[ks];
		}
		if(a[i]==a[ks]) k[i+1]=++ks;
		else k[i+1]=0;
	}
	cout<<n-k[n];
	return 0;
}

T3鬼子进村

题意(李云龙)

给定一些操作,查找,删除,与重建,查找时求连通块长度

思路

数据结构:

数组 b[] 标记房子是否被摧毁(1 表示摧毁,0 表示正常)
栈 s[] 记录被摧毁房子的顺序(用于恢复操作)
变量 t 作为栈顶指针

核心逻辑:

  • 对于 D x 操作:标记 x 号房子为摧毁状态,并将 x 入栈
  • 对于 R 操作:从栈中取出最后一个被摧毁的房子,恢复其正常状态
  • 对于 Q x 操作:
    1. 寻找 x 左边最近的被摧毁房子(记为 l)
    2.寻找 x 右边最近的被摧毁房子(记为 r)
    若 x 本身被摧毁(此时 l 和 r 可能相等),则输出 0
    否则,能到达的房子数量为 r - l - 1(即左右两个摧毁点之间的所有房子)

优势:

利用栈维护摧毁顺序,使得恢复操作(R)可以高效执行
通过遍历栈中元素来查找左右边界,思路直观
时间复杂度主要取决于查询操作中遍历栈的次数,在实际场景中基本能满足需求

  • 例如样例中查询 4 号房子时:
    左边最近的摧毁点是 3,右边最近的摧毁点是 5
    因此能到达的房子是 4,共 1 个(对应第一次查询输出 1)

代码

#include<bits/stdc++.h>
#define N 500050
using namespace std;
int n,m;
int t,s[N];
bool b[N];
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(int i=1; i<=m; i++) {
		char op;
		cin>>op;
		if(op=='R') {
			b[s[t--]]=0;
			continue;
		}
		int a;
		cin>>a;
		if(op=='D') {
			b[a]=1;
			s[++t]=a;
		}
		if(op=='Q'){//二分求联通
			int l=0,r=n+1;
			for(int i=1; i<=t; i++) {
				if(s[i]<=a) l=max(s[i],l);
				if(s[i]>=a) r=min(s[i],r);
			}
			if(l==r) cout<<0<<'\n';
			else cout<<r-l-1<<'\n';
		}
	}
}
posted @ 2025-10-08 08:02  爱做牢笼的老龙  阅读(1)  评论(0)    收藏  举报