【做题记录】csp2025-提高组生成树和笛卡尔树专题

A. [COCI2009-2010#7] SVEMIR

显然 boruvka。将所有点分别按照 \(x\)\(y\)\(z\) 排序,更新最小边。时间复杂度 \(O(n\log^2 n)\)

Code
#include<cstdio>
#include<iostream>
#include<utility>
#include<algorithm>
#include<climits>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,fa[maxn],sz[maxn];
pii mne[maxn];
struct node{
	int x,y,z,id;
}a[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		sz[u]+=sz[v],fa[v]=u;
	}
	else{
		sz[v]+=sz[u],fa[u]=v;
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].x>>a[i].y>>a[i].z;
		a[i].id=fa[i]=i,sz[i]=1;
	}
	ll ans=0;
	for(;;){
//		puts("666");
		for(int i=1;i<=n;i++){
			mne[i]=mp(INT_MAX,0);
		}
		sort(a+1,a+n+1,[](const node &p,const node &q){return p.x<q.x;});
		for(int i=1,u,v,w;i<n;i++){
			u=find(a[i].id),v=find(a[i+1].id),w=a[i+1].x-a[i].x;
			if(u!=v){
				mne[u]=min(mne[u],mp(w,v));
				mne[v]=min(mne[v],mp(w,u));
			}
		}
		sort(a+1,a+n+1,[](const node &p,const node &q){return p.y<q.y;});
		for(int i=1,u,v,w;i<n;i++){
			u=find(a[i].id),v=find(a[i+1].id),w=a[i+1].y-a[i].y;
			if(u!=v){
				mne[u]=min(mne[u],mp(w,v));
				mne[v]=min(mne[v],mp(w,u));
			}
		}
		sort(a+1,a+n+1,[](const node &p,const node &q){return p.z<q.z;});
		for(int i=1,u,v,w;i<n;i++){
			u=find(a[i].id),v=find(a[i+1].id),w=a[i+1].z-a[i].z;
			if(u!=v){
				mne[u]=min(mne[u],mp(w,v));
				mne[v]=min(mne[v],mp(w,u));
			}
		}
		bool flag=0;
		for(int i=1;i<=n;i++){
			if(find(i)==i&&mne[i].fir<INT_MAX&&find(i)!=find(mne[i].sec)){
				flag=1;
				merge(i,mne[i].sec);
				ans+=mne[i].fir;
			}
		}
		if(!flag){
			break;
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. [SCOI2012] 滑雪

对于第一问,按照高度决定连边方向,跑个 bfs 即可。
对于第二问,显然 prim 可做。但是在优先队列内,需要按照点的高度从大到小为第一关键字,距离从小到大为第二关键字。原因是我们建的是一张有向图,有些边从较低的点向较高的点是更新不到的。而如果依次考虑由高向低的点,则一定可以保证每条边都被考虑到,且考虑到每个点时指向它的边一定已经被考虑完了。时间复杂度 \(O(m\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,maxm=1e6+5;
int n,m,a[maxn],fa[maxn],sz[maxn];
ll dis[maxn];
bool vis[maxn],viss[maxn];
vector<pii> e[maxn];
queue<int> q;
struct cmp{
	il bool operator()(const pii &x,const pii &y)const{
		if(a[x.sec]!=a[y.sec]){
			return a[x.sec]<a[y.sec];
		}
		return x.fir>y.fir;
	}
};
priority_queue<pii,vector<pii>,cmp> p;
struct node{
	int u,v,w;
	il bool operator<(const node &x)const{
		return w<x.w;
	}
}ed[maxm<<1];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		sz[u]+=sz[v],fa[v]=u;
	}
	else{
		sz[v]+=sz[u],fa[u]=v;
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
//	cout<<cplx::usdmem();
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		fa[i]=i,sz[i]=1;
	}
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		if(a[u]>=a[v]){
			e[u].pb(mp(v,w));
		}
		if(a[v]>=a[u]){
			e[v].pb(mp(u,w));
		}
	}
	int ans1=0;
	q.push(1),vis[1]=1;
	while(q.size()){
		int u=q.front();
		ans1++,q.pop();
		for(pii i:e[u]){
			int v=i.fir;
			if(!vis[v]){
				q.push(v),vis[v]=1;
			}
		}
	}
	memset(dis,0x3f,sizeof dis);
	dis[1]=0,p.push(mp(0,1));
	while(p.size()){
		int u=p.top().sec;
//		cout<<u<<"\n";
		p.pop(),viss[u]=1;
		for(pii i:e[u]){
			int v=i.fir,w=i.sec;
			if(!vis[v]){
				continue;
			}
			if(!viss[v]&&dis[v]>w){
				dis[v]=w;
				p.push(mp(w,v));
			}
		}
	}
	ll ans2=0;
	for(int i=1;i<=n;i++){
		if(vis[i]){
			ans2+=dis[i];
		}
	}
	cout<<ans1<<" "<<ans2;
	return 0;
}
}
int main(){return asbt::main();}

C. [USACO01OPEN] Earthquake

\[\begin{aligned} &ans=\frac{f-\sum_{i\in S}c_i}{\sum_{i\in S}t_i}\\ \Leftrightarrow&f=\sum_{i\in S}t_i\times ans+c_i \end{aligned} \]

其中 \(S\) 为最终选择的边集。
则对于任意一个生成树 \(K\) 我们有:

\[\begin{aligned} &ans\ge\frac{f-\sum_{i\in K}c_i}{\sum_{i\in K}t_i}\\ \Leftrightarrow&f\le \sum_{i\in K}t_i\times ans+c_i \end{aligned} \]

于是对于任意实数 \(x\),考虑它与 \(ans\) 的大小关系:

  • \(x>ans\)
    \(\forall K,f<\sum_{i\in K}t_i\times x+c_i\)
  • \(x=ans\)
    \(\forall K,f\le\sum_{i\in K}t_i\times x+c_i\)
  • \(x<ans\)
    \(\exist K,f>\sum_{i\in K}t_i\times x+c_i\)

于是二分答案,跑最小生成树 check 即可。时间复杂度 \(O(m\log^2)\)
因为输出要求精确到小数点后第 \(4\) 位,所以要求二分出来的答案精确到小数点后第 \(5\) 位,所以 \(\varepsilon\) 应该赋为 \(10^{-6}\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=405,maxm=1e4+5;
const double eps=1e-6;
int n,m,f,fa[maxn],sz[maxn];
struct node{
	int u,v,c,t;
}e[maxm];
il int find(int x){
	return fa[x]!=x?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		sz[u]+=sz[v],fa[v]=u;
	}
	else{
		sz[v]+=sz[u],fa[u]=v;
	}
}
il double kruskal(double x){
	sort(e+1,e+m+1,[=](const node &p,const node &q){return p.t*x+p.c<q.t*x+q.c;});
	for(int i=1;i<=n;i++){
		fa[i]=i,sz[i]=1;
	}
	double res=0;
	for(int i=1,u,v;i<=m;i++){
		u=e[i].u,v=e[i].v;
		if(find(u)!=find(v)){
			merge(u,v);
			res+=e[i].t*x+e[i].c;
		}
	}
//	cout<<res<<"\n";
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>f;
	for(int i=1;i<=m;i++){
		cin>>e[i].u>>e[i].v>>e[i].c>>e[i].t;
	}
	double l=0,r=1e14;
	while(r-l>eps){
		double mid=(l+r)/2;
//		cout<<mid<<" ";
		if(kruskal(mid)>=f){
			r=mid;
		}
		else{
			l=mid;
		}
	}
	printf("%.4f",l);
	return 0;
}
}
int main(){return asbt::main();}

D. [蓝桥杯 2023 省 A] 网络稳定性

建出最大生成树,然后树剖 \(+\) 线段树链查询最小值即可。时间复杂度 \(O(q\log^2n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,inf=0x3f3f3f3f;
int n,m,q,fa[maxn],sz[maxn],dis[maxn];
int dep[maxn],hes[maxn],top[maxn];
int dfn[maxn],idx[maxn],cnt,tr[maxn<<2];
struct node{
	int u,v,w;
	il bool operator<(const node &x)const{
		return w>x.w;
	}
}a[maxn*3];
vector<pii> e[maxn];
struct{
	int fa[maxn],sz[maxn];
	il void init(){
		for(int i=1;i<=n;i++){
			fa[i]=i,sz[i]=1;
		}
	}
	il int find(int x){
		return x!=fa[x]?fa[x]=find(fa[x]):x;
	}
	il void merge(int u,int v){
		u=find(u),v=find(v);
		if(u==v){
			return ;
		}
		if(sz[u]>sz[v]){
			sz[u]+=sz[v],fa[v]=u;
		}
		else{
			sz[v]+=sz[u],fa[u]=v;
		}
	}
	il bool check(int u,int v){
		return find(u)==find(v);
	}
}D;
il void dfs1(int u){
	dep[u]=dep[fa[u]]+1;
	sz[u]=1;
	int mxs=0;
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa[u]){
			continue;
		}
		fa[v]=u,dis[v]=w;
		dfs1(v);
		sz[u]+=sz[v];
		if(mxs<sz[v]){
			mxs=sz[v],hes[u]=v;
		}
	}
}
il void dfs2(int u){
	dfn[u]=++cnt;
	idx[cnt]=u;
	if(!top[u]){
		top[u]=u;
	}
	if(hes[u]){
		top[hes[u]]=top[u];
		dfs2(hes[u]);
	}
	for(pii i:e[u]){
		int v=i.fir;
		if(v!=fa[u]&&v!=hes[u]){
			dfs2(v);
		}
	}
}
il void pushup(int id){
	tr[id]=min(tr[lid],tr[rid]);
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=dis[idx[l]];
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il int query(int id,int L,int R,int l,int r){
	if(l>r){
		return inf;
	}
	if(L>=l&&R<=r){
		return tr[id];
	}
	int mid=(L+R)>>1,res=inf;
	if(l<=mid){
		res=min(res,query(lid,L,mid,l,r));
	}
	if(r>mid){
		res=min(res,query(rid,mid+1,R,l,r));
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++){
		cin>>a[i].u>>a[i].v>>a[i].w;
	}
	sort(a+1,a+m+1);
	D.init();
	for(int i=1,u,v,w;i<=m;i++){
		u=a[i].u,v=a[i].v,w=a[i].w;
		if(D.find(u)!=D.find(v)){
			D.merge(u,v);
			e[u].pb(mp(v,w));
			e[v].pb(mp(u,w));
		}
	}
	for(int i=1;i<=n;i++){
		if(!dep[i]){
			dfs1(i),dfs2(i);
		}
	}
	build(1,1,n);
	while(q--){
		int u,v;
		cin>>u>>v;
		if(!D.check(u,v)){
			cout<<"-1\n";
		}
		else{
			int res=inf;
			while(top[u]!=top[v]){
				if(dep[top[u]]<dep[top[v]]){
					swap(u,v);
				}
				res=min(res,query(1,1,n,dfn[top[u]],dfn[u]));
				u=fa[top[u]];
			}
			if(dep[u]>dep[v]){
				swap(u,v);
			}
			res=min(res,query(1,1,n,dfn[u]+1,dfn[v]));
			cout<<res<<"\n";
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

E. [THUPC2022 初赛] 最小公倍树

考虑枚举公因子 \(d\)\(d\)\([l,r]\) 之间的倍数形如 \(\{k\times d,(k+1)\times d,\dots,(k+p)\times d\}\),显然所有点都向 \(k\times d\) 连边是最优的。于是边数为 \(O((r-l)\log)\)。然后再跑 kruskal,时间复杂度为 \(O((r-l)\log^2)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define gcd __gcd
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5;
int n,m,fa[maxn],sz[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		sz[u]+=sz[v],fa[v]=u;
	}
	else{
		sz[v]+=sz[u],fa[u]=v;
	}
}
struct node{
	int u,v,w;
	il bool operator<(const node &x)const{
		return w<x.w;
	}
};
vector<node> e;
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		for(int u=(n+i-1)/i*i,v=u+i;v<=m;v+=i){
			e.pb((node){u,v,u/gcd(u,v)*v});
		}
	}
	sort(e.begin(),e.end());
	for(int i=n;i<=m;i++){
		fa[i]=i,sz[i]=1;
	}
	int ans=0;
	for(node i:e){
		int u=i.u,v=i.v,w=i.w;
		if(find(u)!=find(v)){
			merge(u,v),ans+=w;
		}
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

F. [BJWC2010] 严格次小生成树

首先建出最小生成树。
遍历每一条非树边,考虑连上它后形成的环,我们要从这个环上删掉原有的最大的一条边。当然边权和新边不能相等。考虑新边一定 \(\ge\) 原有的边的最大值,因此用倍增维护路径上的最大值和严格次大值即可。同时,新加 \(2\) 条边一定不如新加 \(1\) 条边更优,所以依次枚举非树边是正确的。时间复杂度 \(O(m\log m)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,maxm=3e5+5;
int n,m,dep[maxn],anc[maxn][22];
struct node{
	int mx1,mx2;
	node(int mx1=-1,int mx2=-1):mx1(mx1),mx2(mx2){}
	il node operator+(const node &x)const{
		int hp[7];
		hp[1]=mx1,hp[2]=mx2,hp[3]=x.mx1,hp[4]=x.mx2;
		sort(hp+1,hp+5,greater<int>());
		hp[unique(hp+1,hp+5)-hp]=-1;
		return node(hp[1],hp[2]);
	}
}mxd[maxn][22];
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}a[maxm];
bool vis[maxm];
vector<pii> e[maxn];
il void dfs(int u){
	dep[u]=dep[anc[u][0]]+1;
	for(int i=1;i<=20;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
		mxd[u][i]=mxd[u][i-1]+mxd[anc[u][i-1]][i-1];
	}
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v!=anc[u][0]){
			anc[v][0]=u;
			mxd[v][0].mx1=w;
			dfs(v);
		}
	}
}
il node query(int u,int v){
	if(dep[u]<dep[v]){
		swap(u,v);
	}
	int ddep=dep[u]-dep[v],tmp=0;
	node res;
	while(ddep){
		if(ddep&1){
			res=res+mxd[u][tmp];
			u=anc[u][tmp];
		}
		ddep>>=1,tmp++;
	}
	if(u==v){
		return res;
	}
	for(int i=20;~i;i--){
		if(anc[u][i]!=anc[v][i]){
			res=res+mxd[u][i]+mxd[v][i];
			u=anc[u][i],v=anc[v][i];
		}
	}
	return res+mxd[u][0]+mxd[v][0];
}
struct{
	int fa[maxn],sz[maxn];
	il void init(){
		for(int i=1;i<=n;i++){
			fa[i]=i,sz[i]=1;
		}
	}
	il int find(int x){
		return x!=fa[x]?fa[x]=find(fa[x]):x;
	}
	il void merge(int u,int v){
		u=find(u),v=find(v);
		if(u==v){
			return ;
		}
		if(sz[u]>sz[v]){
			sz[u]+=sz[v],fa[v]=u;
		}
		else{
			sz[v]+=sz[u],fa[u]=v;
		}
	}
	il bool check(int u,int v){
		return find(u)==find(v);
	}
}D;
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
//	cout<<cplx::usdmem()<<"\n";
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i].u>>a[i].v>>a[i].w;
	}
	sort(a+1,a+m+1),D.init();
	ll ans1=0,ans2=1e18;
	for(int i=1,u,v,w;i<=m;i++){
		u=a[i].u,v=a[i].v,w=a[i].w;
		if(!D.check(u,v)){
			D.merge(u,v);
			vis[i]=1,ans1+=w;
			e[u].pb(mp(v,w));
			e[v].pb(mp(u,w));
		}
	}
	dfs(1);
	for(int i=1,u,v,w;i<=m;i++){
		u=a[i].u,v=a[i].v,w=a[i].w;
		if(vis[i]){
			continue;
		}
		node tmp=query(u,v);
		int hp[7];
		hp[1]=tmp.mx1,hp[2]=tmp.mx2,hp[3]=w;
		sort(hp+1,hp+4,greater<int>());
		hp[unique(hp+1,hp+4)-hp]=-1;
		if(~hp[2]){
			ans2=min(ans2,ans1-hp[2]+hp[1]);
		}
	}
	cout<<ans2;
	return 0;
}
}
int main(){return asbt::main();}

G. [APIO2013] 道路费用

\(k\le 20\),考虑 \(O(2^k)\) 暴力枚举加入的边。但是边数很大,时间复杂度很高无法承受。
考虑在一开始强制选这 \(k\) 条边,然后跑最小生成树,此时加入的边就是一定会加入的边。设这个边集为 \(S\)
\(S\) 连接的连通块缩成点,点数为 \(O(k)\)。再在原图上对这些点跑最小生成树,设加入的边集为 \(T\),则 \(T\) 为加入那 \(k\) 条边后有可能在最小生成树中的边。数量也为 \(O(k)\)
然后暴力枚举强制加入的边,用 \(T\) 跑出最小生成树,再用 \(T\) 中的非树边限制强制加入的边即可。具体地,对于一条非树边 \((u,v)\),树上 \(u\)\(v\) 的路径中的所有边都应该 \(\ge\) 这条非树边的边权。暴力跳父亲即可。
考虑统计答案,只需要计算通过每条边的人数,用 dfs 计算子树权值和即可。
时间复杂度 \(O(m\log m+2^kk^2)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=3e5+5;
int n,m,k,a[maxn],bel[maxn];
bool vis[maxn];
vector<int> dk;
vector<pii> e[maxn];
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}em[maxn],ek[maxn];
vector<edge> et;
int fa[maxn],sz[maxn],ans;
int mxe[maxn],dep[maxn],tof[maxn];
il void init(){
	for(int i=1;i<=n;i++){
		fa[i]=i,sz[i]=1;
	}
}
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		sz[u]+=sz[v],fa[v]=u;
	}
	else{
		sz[v]+=sz[u],fa[u]=v;
	}
}
il void dfs(int u){
	dep[u]=dep[fa[u]]+1;
	sz[u]=a[u];
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v!=fa[u]){
			fa[v]=u;
			tof[v]=w;
			dfs(v);
			sz[u]+=sz[v];
		}
	}
}
il void solve(int S){
	for(int u:dk){
		fa[u]=u,sz[u]=1,e[u].clear();
	}
	for(int i=1,u,v;i<=k;i++){
		if(S>>(i-1)&1){
			u=bel[ek[i].u],v=bel[ek[i].v];
			if(find(u)==find(v)){
				return ;
			}
			merge(u,v);
			e[u].pb(mp(v,-i));
			e[v].pb(mp(u,-i));
		}
	}
	for(int i=0;i<et.size();i++){
		vis[i]=0;
	}
	for(int i=0,u,v,w;i<et.size();i++){
		u=et[i].u,v=et[i].v,w=et[i].w;
		if(find(u)!=find(v)){
			merge(u,v);
			vis[i]=1;
			e[u].pb(mp(v,w));
			e[v].pb(mp(u,w));
		}
	}
	for(int i=1;i<=k;i++){
		mxe[i]=INT_MAX;
	}
	for(int u:dk){
		dep[u]=tof[u]=fa[u]=sz[u]=0;
	}
	dfs(bel[1]);
//	puts("666");
	for(int i=0,u,v,w;i<et.size();i++){
//		cout<<i<<"\n";
		if(vis[i]){
			continue;
		}
		u=et[i].u,v=et[i].v,w=et[i].w;
		if(dep[u]<dep[v]){
			swap(u,v);
		}
//		cout<<dep[u]<<" "<<dep[v]<<"\n";
		while(dep[u]>dep[v]){
//			puts("666");
			if(tof[u]<0){
				mxe[-tof[u]]=min(mxe[-tof[u]],w);
			}
			u=fa[u];
		}
		while(u!=v){
			if(tof[u]<0){
				mxe[-tof[u]]=min(mxe[-tof[u]],w);
			}
			if(tof[v]<0){
				mxe[-tof[v]]=min(mxe[-tof[v]],w);
			}
			u=fa[u],v=fa[v];
		}
	}
	for(int u:dk){
		if(tof[u]<0){
			tof[u]=-mxe[-tof[u]];
		}
	}
	int res=0;
	for(int u:dk){
		if(tof[u]<0){
			res-=tof[u]*sz[u];
		}
	}
	ans=max(ans,res);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
//	cout<<cplx::usdmem();
	ios::sync_with_stdio(0),cin.tie(0);
//	freopen("toll5.in","r",stdin);
	cin>>n>>m>>k;
//	cout<<n<<" "<<m<<" "<<k<<"\n";
	for(int i=1;i<=m;i++){
		cin>>em[i].u>>em[i].v>>em[i].w;
	}
	init(),sort(em+1,em+m+1);
	for(int i=1;i<=k;i++){
		cin>>ek[i].u>>ek[i].v;
		merge(ek[i].u,ek[i].v);
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,u,v;i<=m;i++){
		u=em[i].u,v=em[i].v;
		if(find(u)!=find(v)){
			merge(u,v);
			vis[i]=1;
		}
	}
	init();
	for(int i=1,u,v;i<=m;i++){
		if(vis[i]){
			u=find(em[i].u);
			v=find(em[i].v);
			if(sz[u]>sz[v]){
				sz[u]+=sz[v];
				a[u]+=a[v];
				fa[v]=u;
			}
			else{
				sz[v]+=sz[u];
				a[v]+=a[u];
				fa[u]=v;
			}
		}
	}
	for(int i=1;i<=n;i++){
		bel[i]=find(i);
		if(bel[i]==i){
			dk.pb(i);
		}
	}
	init();
	for(int i=1,u,v,w;i<=m;i++){
		u=bel[em[i].u],v=bel[em[i].v],w=em[i].w;
		if(find(u)!=find(v)){
			et.pb((edge){u,v,w});
			merge(u,v);
		}
	}
	for(int S=0;S<1<<k;S++){
//		cout<<bitset<15>(S)<<"\n";
		solve(S);
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}
/*
100000 299989 12
*/

H. [蓝桥杯 2015 省 A] 灾后重建

建 kruskal 重构树,问题转化为求 \(\operatorname{lca}\)
根号分治,对于 \(k>\sqrt{n}\) 暴力求,剩下的对于剩余系去建线段树区间查询。时间复杂度 \(O(q\sqrt{n})\)
然而空间十分爆炸。原因是对于每个剩余系都建线段树是无法接受的。因此将查询离线下来,就可以只建一棵线段树,不断重构即可。然而原题数据没有卡这一点。
空间不正确的代码:

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define lwrb lower_bound
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5,maxb=250;
int n,m,q,blen,fa[maxn],zhi[maxn];
vector<int> e[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}a[maxn];
int dfn[maxn],oula[maxn],idx[maxn],cnt;
il void dfs(int u){
	dfn[u]=++cnt;
	oula[cnt]=cnt;
	idx[cnt]=u;
	for(int v:e[u]){
		if(v!=fa[u]){
			dfs(v);
			oula[++cnt]=dfn[u];
		}
	}
}
struct{
	int Log[maxn],st[maxn][22];
	il void init(){
		for(int i=2;i<=cnt;i++){
			Log[i]=Log[i>>1]+1;
		}
		for(int i=1;i<=cnt;i++){
			st[i][0]=oula[i];
		}
		for(int j=1;j<=Log[cnt];j++){
			for(int i=1;i+(1<<j)-1<=cnt;i++){
				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
	}
	il int query(int l,int r){
		int tmp=Log[r-l+1];
		return min(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
	}
}ST;
il int lca(int u,int v){
	if(dfn[u]>dfn[v]){
		swap(u,v);
	}
	return idx[ST.query(dfn[u],dfn[v])];
}
vector<int> syx[maxb][maxb];
struct stree{
	#define lid id<<1
	#define rid id<<1|1
	int num;
	vector<int> tr;
	il void pushup(int id){
		tr[id]=lca(tr[lid],tr[rid]);
	}
	il void build(int id,int l,int r,int x,int y){
		if(l>r){
			return ;
		}
		if(l==r){
			tr[id]=syx[x][y][l];
			return ;
		}
		int mid=(l+r)>>1;
		build(lid,l,mid,x,y);
		build(rid,mid+1,r,x,y);
		pushup(id);
	}
	stree(int x=0,int y=0):num(syx[x][y].size()),tr(num<<2,0){
//		puts("666");
		build(1,0,num-1,x,y);
	}
	il int query(int id,int L,int R,int l,int r){
		if(L>=l&&R<=r){
			return tr[id];
		}
		int mid=(L+R)>>1;
		if(r<=mid){
			return query(lid,L,mid,l,r);
		}
		if(l>mid){
			return query(rid,mid+1,R,l,r);
		}
		return lca(query(lid,L,mid,l,r),query(rid,mid+1,R,l,r));
	}
	il int query(int id,int l,int r){
		return query(1,0,num-1,l,r);
	}
	#undef lid
	#undef rid
}SG[maxb][maxb];
bool vis[maxb][maxb];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
//	freopen("H.in","r",stdin);
//	freopen("H.out","w",stdout);
//	cout<<cplx::usdmem();
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++){
		cin>>a[i].u>>a[i].v>>a[i].w;
	}
	sort(a+1,a+m+1);
	int tot=n;
	for(int i=1;i<=n<<1;i++){
		fa[i]=i;
	}
	for(int i=1,u,v,w;i<=m;i++){
//	puts("666");
		u=a[i].u,v=a[i].v,w=a[i].w;
		u=find(u),v=find(v);
		if(u!=v){
			fa[u]=fa[v]=++tot;
			zhi[tot]=w;
			e[tot].pb(u),e[tot].pb(v);
		}
	}
	dfs(tot),ST.init();
	blen=sqrt(n);
//	cout<<blen<<"\n";
//	bool begin;
//	int num=0;
	for(int i=1;i<=blen;i++){
		for(int j=1;j<=n;j++){
			syx[i][j%i].pb(j);
//			num++;
		}
	}
//	cout<<num<<"\n";
//	bool end;
//	cout<<(&end-&begin)/1048576.0<<"\n";
	while(q--){
		int l,r,k,c;
		cin>>l>>r>>k>>c;
		if(k>blen){
			int tmp=l;
			if(l%k<c){
				tmp+=c-l%k;
			}
			else if(l%k>c){
				tmp-=l%k-c;
				tmp+=k;
			}
			for(int i=tmp+k;i<=r;i+=k){
				tmp=lca(tmp,i);
			}
			cout<<zhi[tmp]<<"\n";
			continue;
		}
		if(!vis[k][c]){
			SG[k][c]=stree(k,c);
			vis[k][c]=1;
		}
		cout<<zhi[SG[k][c].query(1,lwrb(syx[k][c].begin(),syx[k][c].end(),l)-syx[k][c].begin(),uprb(syx[k][c].begin(),syx[k][c].end(),r)-syx[k][c].begin()-1)]<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

离线卡空间的代码:

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define lwrb lower_bound
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5,maxb=250;
int n,m,q,blen,fa[maxn],zhi[maxn];
vector<int> e[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}a[maxn];
int dfn[maxn],oula[maxn],idx[maxn],cnt;
il void dfs(int u){
	dfn[u]=++cnt;
	oula[cnt]=cnt;
	idx[cnt]=u;
	for(int v:e[u]){
		if(v!=fa[u]){
			dfs(v);
			oula[++cnt]=dfn[u];
		}
	}
}
struct{
	int Log[maxn],st[maxn][22];
	il void init(){
		for(int i=2;i<=cnt;i++){
			Log[i]=Log[i>>1]+1;
		}
		for(int i=1;i<=cnt;i++){
			st[i][0]=oula[i];
		}
		for(int j=1;j<=Log[cnt];j++){
			for(int i=1;i+(1<<j)-1<=cnt;i++){
				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
	}
	il int query(int l,int r){
		int tmp=Log[r-l+1];
		return min(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
	}
}ST;
il int lca(int u,int v){
	if(dfn[u]>dfn[v]){
		swap(u,v);
	}
	return idx[ST.query(dfn[u],dfn[v])];
}
vector<int> syx[maxb][maxb];
struct stree{
	#define lid id<<1
	#define rid id<<1|1
	int num,tr[maxn<<2];
	il void pushup(int id){
		tr[id]=lca(tr[lid],tr[rid]);
	}
	il void build(int id,int l,int r,int x,int y){
		if(l>r){
			return ;
		}
		if(l==r){
			tr[id]=syx[x][y][l];
			return ;
		}
		int mid=(l+r)>>1;
		build(lid,l,mid,x,y);
		build(rid,mid+1,r,x,y);
		pushup(id);
	}
	stree(int x=0,int y=0):num(syx[x][y].size()){
//		puts("666");
		build(1,0,num-1,x,y);
	}
	il int query(int id,int L,int R,int l,int r){
		if(L>=l&&R<=r){
			return tr[id];
		}
		int mid=(L+R)>>1;
		if(r<=mid){
			return query(lid,L,mid,l,r);
		}
		if(l>mid){
			return query(rid,mid+1,R,l,r);
		}
		return lca(query(lid,L,mid,l,r),query(rid,mid+1,R,l,r));
	}
	il int query(int id,int l,int r){
		return query(1,0,num-1,l,r);
	}
	#undef lid
	#undef rid
}SG;
struct node{
	int l,r,k,c,id;
	il bool operator<(const node &x)const{
		return k<x.k||k==x.k&&c<x.c;
	}
}wt[maxn];
int ans[maxn];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
//	freopen("H.in","r",stdin);
//	freopen("H.out","w",stdout);
//	cout<<cplx::usdmem();
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++){
		cin>>a[i].u>>a[i].v>>a[i].w;
	}
	sort(a+1,a+m+1);
	int tot=n;
	for(int i=1;i<=n<<1;i++){
		fa[i]=i;
	}
	for(int i=1,u,v,w;i<=m;i++){
//	puts("666");
		u=a[i].u,v=a[i].v,w=a[i].w;
		u=find(u),v=find(v);
		if(u!=v){
			fa[u]=fa[v]=++tot;
			zhi[tot]=w;
			e[tot].pb(u),e[tot].pb(v);
		}
	}
	dfs(tot),ST.init();
	blen=sqrt(n);
//	cout<<blen<<"\n";
//	bool begin;
//	int num=0;
	for(int i=1;i<=blen;i++){
		for(int j=1;j<=n;j++){
			syx[i][j%i].pb(j);
//			num++;
		}
	}
//	cout<<num<<"\n";
//	bool end;
//	cout<<(&end-&begin)/1048576.0<<"\n";
	for(int i=1;i<=q;i++){
		cin>>wt[i].l>>wt[i].r>>wt[i].k>>wt[i].c;
		wt[i].id=i;
	}
	sort(wt+1,wt+q+1);
	for(int i=1;i<=q;i++){
		int l=wt[i].l,r=wt[i].r,k=wt[i].k,c=wt[i].c;
		if(k>blen){
			int tmp=l;
			if(l%k<c){
				tmp+=c-l%k;
			}
			else if(l%k>c){
				tmp-=l%k-c;
				tmp+=k;
			}
			for(int i=tmp+k;i<=r;i+=k){
				tmp=lca(tmp,i);
			}
			ans[wt[i].id]=zhi[tmp];
			continue;
		}
		if(k!=wt[i-1].k||c!=wt[i-1].c){
			SG=stree(k,c);
		}
		ans[wt[i].id]=zhi[SG.query(1,lwrb(syx[k][c].begin(),syx[k][c].end(),l)-syx[k][c].begin(),uprb(syx[k][c].begin(),syx[k][c].end(),r)-syx[k][c].begin()-1)];
	}
	for(int i=1;i<=q;i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

I. Mathematics Curriculum

子段最大值个数即为这个点在大根笛卡尔树中的深度。考虑建树过程即可证明。
\(f_{i,j,k}\) 表示 \(i\) 个点的笛卡尔树,其中 \(i\) 是最大的点,第 \(k\) 层有 \(j\) 个点。枚举左右子树即可得到方程:

\[f_{i,j,k}=\sum_{x=0}^{i-1}\sum_{y=0}^{j}{i-1\choose{x}}\times f_{x,y,k-1}\times f_{i-x-1,j-y,k-1} \]

边界条件 \(f_{i,1,1}=i!\)。转移是 \(O(n^5)\) 的,考虑优化。

  • 对于 \(k>i\),只有 \(f_{i,0,k}=i!\),其它值都为 \(0\)
  • 三个乘数都不为 \(0\) 时转移才有效。

于是就可以通过。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=105;
int n,m,num,mod;
int fac[maxn],C[maxn][maxn];
int f[maxn][maxn][maxn];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
//	freopen("I.in","r",stdin);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>num>>mod;
	fac[0]=C[0][0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
		C[i][0]=1;
		for(int j=1;j<=i;j++){
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
		}
	}
	for(int i=0;i<=m;i++){
		f[0][0][i]=1;
	}
	f[1][0][0]=f[1][1][1]=1;
	for(int i=2;i<=m;i++){
		f[1][0][i]=1;
	}
	for(int i=2;i<=n;i++){
		f[i][1][1]=fac[i];
		for(int k=2;k<=m;k++){
			if(k>i){
				f[i][0][k]=fac[i];
				continue;
			}
			for(int j=0;j<=i;j++){
				for(int x=0;x<i;x++){
					if(!C[i-1][x]){
						continue;
					}
					for(int y=0;y<=j;y++){
						if(f[x][y][k-1]&&f[i-1-x][j-y][k-1]){
							(f[i][j][k]+=C[i-1][x]*1ll*f[x][y][k-1]%mod*f[i-1-x][j-y][k-1]%mod)%=mod;
						}
					}
				}
			}
		}
	}
	cout<<f[n][num][m];
	return 0;
}
}
int main(){return asbt::main();}

J. 「OICon-02」maxiMINImax

用单调栈可以求出以 \(a_i\) 为最小值和最大值的区间个数 \(qmn_i\)\(qmx_i\)
从小到大枚举第二个区间的最小值,记 \(p_i\) 表示 \(i\) 的位置,则对于 \(i\) 的答案即为:

\[\sum_{j=1}^{p_i-1}\sum_{k=p_i+1}^{n}{qmn_{p_i}qmx_jqmx_k(i-a_j)(i-a_k)} \]

简单推式子后得到:
\( qmn_{p_i}(\sum{qmx_j}\sum{qmx_k}i^2-\sum{qmx_ja_j}\sum{qmx_k}i-\sum{qmx_j}\sum{qmx_ka_k}i+\sum{qmx_ja_j}\sum{qmx_ka_k}) \)
于是开两棵树状数组维护 \(qmx_j\)\(qmx_ja_j\) 就好了。时间复杂度 \(O(n\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5,mod=9712176;
int n,a[maxn],p[maxn],zhan[maxn];
int lmn[maxn],rmn[maxn];
int lmx[maxn],rmx[maxn];
int qmn[maxn],qmx[maxn];
struct{
	int tr[maxn];
	il int lowbit(int x){
		return x&-x;
	}
	il void upd(int p,int v){
		for(;p<=n;p+=lowbit(p)){
			(tr[p]+=v)%=mod;
		}
	}
	il int query(int p){
		int res=0;
		for(;p;p-=lowbit(p)){
			(res+=tr[p])%=mod;
		}
		return res;
	}
	il int query(int l,int r){
		return (query(r)-query(l-1)+mod)%mod;
	}
}F1,F2;
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		p[a[i]]=i;
	}
	for(int i=1,top=0;i<=n;i++){
		while(top&&a[zhan[top]]>a[i]){
			top--;
		}
		lmn[i]=zhan[top];
		zhan[++top]=i;
	}
	for(int i=1,top=0;i<=n;i++){
		while(top&&a[zhan[top]]<a[i]){
			top--;
		}
		lmx[i]=zhan[top];
		zhan[++top]=i;
	}
	zhan[0]=n+1;
	for(int i=n,top=0;i;i--){
		while(top&&a[zhan[top]]>a[i]){
			top--;
		}
		rmn[i]=zhan[top];
		zhan[++top]=i;
	}
	for(int i=n,top=0;i;i--){
		while(top&&a[zhan[top]]<a[i]){
			top--;
		}
		rmx[i]=zhan[top];
		zhan[++top]=i;
	}
	for(int i=1;i<=n;i++){
		qmn[i]=(i-lmn[i])*1ll*(rmn[i]-i)%mod;
		qmx[i]=(i-lmx[i])*1ll*(rmx[i]-i)%mod;
	}
	int ans=0;
	for(int i=1,xj,xaj,xk,xak;i<=n;i++){
		xj=F1.query(1,p[i]-1);
		xaj=F2.query(1,p[i]-1);
		xk=F1.query(p[i]+1,n);
		xak=F2.query(p[i]+1,n);
		(ans+=qmn[p[i]]*1ll*(xj*1ll*xk%mod*i%mod*i%mod-xaj*1ll*xk%mod*i%mod-xj*1ll*xak%mod*i%mod+xaj*1ll*xak%mod)%mod)%=mod;
		(ans+=mod)%=mod;
		F1.upd(p[i],qmx[p[i]]);
		F2.upd(p[i],qmx[p[i]]*1ll*i%mod);
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2025-02-04 08:21  zhangxy__hp  阅读(64)  评论(0)    收藏  举报