2025.2.20-2.25做题记录

补题内容:最小生成树,拓扑排序,直径,重心,LCA

游览

首先应想到乘法原理:一条边能做出的贡献为:该边的权值 \(\times\) 经历该边的路径方案数。而路径方案数的统计可以用 \(cnt\) 数组解决,正常拓扑即可。拓扑是为了避免计算未统计完信息的节点。注意别少取模了,少一个都 WA 。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
const int N=1e4+50;
const int M=5e4+50;
const int p=10000;
struct Edge{
	int to,nxt,w;
}e[M];
int head[M],tot;
int cnt[N],dis[N];
int in[N];
queue<int> que;
void add(int u,int v,int w){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	e[tot].w=w;
	head[u]=tot;
	return ;
}
int n,m,s,t,t0;
signed main(){
	cin>>n>>m>>s>>t>>t0;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		in[v]++;
	}
	cnt[s]=1;
	que.push(s);
	for(int i=1;i<=n;i++){
		if(in[i]==0 && i!=s){
			que.push(i);
		}
	}
	while(!que.empty()){
		int u=que.front();
		que.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			in[v]--;
			dis[v]=dis[v]%p+dis[u]%p+e[i].w%p*cnt[u]%p;
			dis[v]%=p;
			cnt[v]=(cnt[v]%p+cnt[u]%p)%p;
			if(in[v]==0){
				que.push(v);
			}
		}
	}
	cout<<(dis[t]%p+t0%p*(cnt[t]-1)%p)%p;
	return 0;
}

兽径管理

对于每一周重新跑一遍 kruskal 显然是 \(O(w^2logw)\) 的复杂度,秉持着不如试一试的态度,发现过不了。考虑优化:不能离线处理, kruskal 需要排序,所以理论复杂度不能优化了。那么考虑减少 kruskal 的次数,时光倒流!加边改为删边。记录每一次最小生成树的用的边,只要没有删到用的边,就直接继承答案,否则重新计算,如果某一次计算时不连通,那么之前也一定不连通。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=300;
int n,w;
int fa[N];
int find(int x){
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
struct node{
	int x,y,w,id;
}e[12050],ee[12050];
bool vis[6050],use[6050];
int ans[6050];
bool cmp(node a,node b){
	return a.w<b.w;
}
int kruskal(){
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	for(int i=1;i<=w;i++){
		vis[i]=0;
	}
	int ans=0,sum=0;
	for(int i=1;i<=w;i++){
		if(use[e[i].id]) continue;
		int p=find(e[i].x);
		int q=find(e[i].y);
		if(p==q) continue;
		vis[e[i].id]=1;
		fa[p]=q;
		ans+=e[i].w;
		sum++;
	}
	if(sum<n-1) return -1;
	else return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>w;
	for(int i=1;i<=w;i++){
		int x,y,w;
		cin>>x>>y>>w;
		e[i].x=x;
		e[i].y=y;
		e[i].w=w;
		e[i].id=i;
	}
	sort(e+1,e+1+w,cmp);
	ans[w]=kruskal();
	for(int i=w-1;i>=1;i--){
		use[i+1]=1;
		if(vis[i+1]){
			ans[i]=kruskal();
		}
		else{
			ans[i]=ans[i+1];
		}
		if(ans[i]==-1){
			for(int j=1;j<i;j++){
				ans[j]=-1;
			}
		}
	}
	for(int i=1;i<=w;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}

松鼠的新家

树上差分板子题:统计答案就跑一遍 dfs 即可。

树上差分:利用差分数组的思想,肯定会在 x 和 y 上加1,考虑在哪里消除影响呢?他们的 lca 会加两遍,所以要在 lca 上减一,那么 lca 以上是不是也会受影响?是的,所以要在 lca 的父亲上减一,那么本题就完成了。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int N=3e5+50;
int n,a[N],dis[N],t;
int dep[N],f[N][30];
int ans[N];
vector<int> vec[N];
void dfs1(int u,int fa){
	dep[u]=dep[fa]+1;
	f[u][0]=fa;
	for(int i=1;(1<<i)<=dep[u];i++){
		f[u][i]=f[f[u][i-1]][i-1];
	}
	for(int i=0;i<vec[u].size();i++){
		int v=vec[u][i];
		if(v==fa) continue;
		dfs1(v,u);
	}
	return ;
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	int temp=dep[x]-dep[y];
	for(int i=0;(1<<i)<=temp;i++){
		if((1<<i)&temp){
			x=f[x][i];
		}
	}
	if(x==y) return x;
	for(int i=t;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
void dfs(int u,int fa){
	ans[u]=dis[u];
	for(int i=0;i<vec[u].size();i++){
		int v=vec[u][i];
		if(v==fa) continue;
		dfs(v,u);
		ans[u]+=ans[v];
	}
	return;
}
int main(){
	cin>>n;
	t=log2(n)+1;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs1(a[1],0);
	dis[a[1]]=1;
	for(int i=2;i<=n;i++){
		int l=lca(a[i-1],a[i]);
		dis[a[i-1]]++;
		dis[a[i]]++;
		dis[l]--;
		dis[f[l][0]]--;
	}
	dfs(a[1],0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]-1<<'\n';
	}
	return 0;
}

树上询问

观察性质后分讨题。

如果两个点的 lca 就是 c 点,那么太轻松了,只要不是包含 a ,b 的子树的节点,都可以作为根节点出现。

如果 c 在 x 去 lca 的路径上,那么答案就是 c 的大小 \(-\) lca 的子树中包含 a 的子树大小。(可能有点抽象,画画图,看看代码)。

如果 c 在 y 去 lca 的路径上,同上。

如果都不是,那么就是无解,输出 \(0\)

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int N=5e5+50;
vector<int> vec[N];
int n,q,cnt;
int dfn[N],siz[N],dep[N],f[N][35];
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	siz[u]=1;
	f[u][0]=fa;
	dfn[u]=++cnt;
	for(int i=1;(1<<i)<=dep[u];i++){
		f[u][i]=f[f[u][i-1]][i-1];
	}
	for(int i=0;i<vec[u].size();i++){
		int v=vec[u][i];
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
	return ;
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=30;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]){
			x=f[x][i];
		}
	}
	if(x==y) return x;
	for(int i=30;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
int asksiz(int u,int fa){
	if(u==fa) return 0;
	for(int i=30;i>=0;i--){
		if(dfn[f[u][i]]>dfn[fa]){
			u=f[u][i];
		}
	}
	return siz[u];
}
bool is(int u,int fa){
	return (dfn[fa]<=dfn[u] && dfn[fa]+siz[fa]-1>=dfn[u]);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=q;i++){
		int a,b,c;
		cin>>a>>b>>c;
		if(lca(a,b)==c){
			cout<<n-asksiz(a,c)-asksiz(b,c)<<"\n";
		}else if(is(a,c) && !is(b,c)){
			cout<<siz[c]-asksiz(a,c)<<'\n';
		}else if(is(b,c) && !is(a,c)){
			cout<<siz[c]-asksiz(b,c)<<'\n';
		}else{
			cout<<0<<'\n';
		}
	}
	return 0;
}

货车运输

读完题发现不会。考虑对于两个点,选择他们之间的路径时,我们一定会保留最小值最大的路径,删去其余路径。那么考虑构建最大生成树,能保证在不改变连通的情况下,保留下来尽可能大的边,然后就可以做 lca 了。对于求路径上的最小值,做法与 lca 类似,\(w{_i}{_j}\) 表示以 \(i\) 为节点,向上跳 \(2^j\) 期间的最小值。

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=1e5+50;
const int M=5e5+50;
int n,m,q;
struct Edge{
	int to,nxt,w;
}e[2*M];
int head[2*M],tot;
struct node{
	int x,y,w;
}a[2*M];
bool cmp(node a,node b){
	return a.w>b.w;
}
void add(int u,int v,int w){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	e[tot].w=w;
	head[u]=tot;
	return ;
}
int dep[N],fa[N],f[N][30],w[N][30];
bool vis[N];
int find(int x){
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
void dfs(int u){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v]) continue;
		vis[v]=1;
		dep[v]=dep[u]+1;
		f[v][0]=u;
		w[v][0]=e[i].w;
		dfs(v);
	}
	return ;
}
void kruskal(){
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	for(int i=1;i<=m;i++){
		int p=find(a[i].x);
		int q=find(a[i].y);
		if(p==q) continue;
		fa[p]=q;
		add(a[i].x,a[i].y,a[i].w);
		add(a[i].y,a[i].x,a[i].w);
	}
	return ;
}
int lca(int x,int y){
	if(find(x)!=find(y)) return -1;
	int ans=2e9;
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=25;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]){
			ans=min(ans,w[x][i]);
			x=f[x][i];
		}
	}
//	cout<<"q"<<ans<<endl;
	if(x==y) return ans;
	for(int i=25;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			ans=min(ans,min(w[x][i],w[y][i]));
			x=f[x][i];
			y=f[y][i];
		}
	}
	ans=min(ans,min(w[x][0],w[y][0]));
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y,w;
		cin>>x>>y>>w;
		a[i].x=x;
		a[i].y=y;
		a[i].w=w;
	}
	kruskal();
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			dep[i]=1;
			vis[i]=1;
			dfs(i);
			f[i][0]=i;
			w[i][0]=2e9;
		}
	}
	for(int i=1;i<=25;i++){
		for(int j=1;j<=n;j++){
			f[j][i]=f[f[j][i-1]][i-1];
			w[j][i]=min(w[f[j][i-1]][i-1],w[j][i-1]);
		}
	}
	cin>>q;
	for(int i=1;i<=q;i++){
		int x,y;
		cin>>x>>y;
		int ans=lca(x,y);
		cout<<ans<<'\n';
	}
	return 0;
}
posted @ 2025-02-25 17:57  Tighnari  阅读(18)  评论(0)    收藏  举报