【做题记录】2025刷题计划--树上问题

A. Minimum spanning tree for each edge

先建出最小生成树,对于树边答案就是最小生成树,对于非树边就从两个端点的路径上删掉权值最大的即可。
证明:在这个环中,首先强制选了这条边,然后按照从小到大的顺序选边,则一定不会选到删掉的那条边。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#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=2e5+5;
int n,m,fa[maxn],sz[maxn],dep[maxn];
int anc[maxn][22],mxv[maxn][22],ans[maxn];
bool vis[maxn];
struct edge{
	int u,v,w,id;
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}a[maxn];
vector<pii> e[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;
	}
}
il void dfs(int u){
//	cout<<u<<" "<<anc[u][0]<<"\n";
	for(int i=1;i<=20;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
		mxv[u][i]=max(mxv[u][i-1],mxv[anc[u][i-1]][i-1]);
	}
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==anc[u][0]){
			continue;
		}
		anc[v][0]=u,mxv[v][0]=w;
		dep[v]=dep[u]+1;
		dfs(v);
	}
}
il int query(int u,int v){
//	cout<<u<<" "<<v<<"\n";
	if(dep[u]<dep[v]){
		swap(u,v);
	}
	int ddep=dep[u]-dep[v],tmp=0,res=0;
	while(ddep){
		if(ddep&1){
			res=max(res,mxv[u][tmp]);
			u=anc[u][tmp];
		}
		ddep>>=1,tmp++;
	}
//	cout<<u<<" "<<v<<"\n";
	if(u==v){ 
//		puts("666");
		return res;
	}
	for(int i=20;~i;i--){
		if(anc[u][i]!=anc[v][i]){
			res=max({res,mxv[u][i],mxv[v][i]});
			u=anc[u][i],v=anc[v][i];
		}
	}
	return max({res,mxv[u][0],mxv[v][0]});
}
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++){
		cin>>a[i].u>>a[i].v>>a[i].w;
		a[i].id=i;
	}
	sort(a+1,a+m+1);
	for(int i=1;i<=n;i++){
		fa[i]=i,sz[i]=1;
	}
	int res=0;
	for(int i=1,u,v,w;i<=m;i++){
		u=a[i].u,v=a[i].v,w=a[i].w;
		if(find(u)!=find(v)){
			merge(u,v);
			vis[i]=1,res+=w;
			e[u].pb(mp(v,w));
			e[v].pb(mp(u,w));
		}
	}
//	for(int i=1;i<=m;i++){
//		if(vis[i]){
//			cout<<a[i].id<<" ";
//		}
//	}
//	puts("");
	dfs(1);
//	for(int u=1;u<=n;u++){
//		for(int i=0;i<=3;i++){
//			cout<<mxv[u][i]<<" ";
//		}
//		puts("");
//	}
	for(int i=1;i<=m;i++){
		if(vis[i]){
			ans[a[i].id]=res;
		}
		else{
//			cout<<a[i].id<<" "<<query(a[i].u,a[i].v)<<"\n";
			ans[a[i].id]=res-query(a[i].u,a[i].v)+a[i].w;
		}
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}

B. Information Reform

\(f_{u,i}\) 表示 \(u\) 接受 \(i\) 的信号,\(u\) 的子树内的答案。那么可以枚举 \(u\) 的儿子 \(v\) 接受信号的节点来转移。注意当 \(v\) 也枚举到 \(i\) 时要减去重复的 \(k\)
考虑构造方案,设 \(ans_u\) 表示答案。首先可以求出 \(ans_1\)。对于 \(u\) 的每个儿子 \(v\),计算 \(u\) 是怎么从 \(v\) 转移来的即可。仍然注意当 \(v\) 枚举到 \(ans_u\) 时要减去重复的 \(k\)
用 Floyd 计算两两之间的距离,时间复杂度 \(O(n^3)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=205,inf=0x3f3f3f3f3f3f3f3f;
int n,m,a[maxn],ans[maxn];
int dis[maxn][maxn];
int f[maxn][maxn];
vector<int> e[maxn];
il void dfs1(int u,int fa){
	for(int i=1;i<=n;i++){
		f[u][i]=a[dis[u][i]]+m;
	}
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs1(v,u);
		for(int i=1,tmp;i<=n;i++){
			tmp=f[u][i];
			f[u][i]+=f[v][i]-m;
			for(int j=1;j<=n;j++){
				f[u][i]=min(f[u][i],tmp+f[v][j]);
			}
		}
	}
}
il void dfs2(int u,int fa){
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		int res=inf;
		for(int i=1;i<=n;i++){
			if(res>f[v][i]){
				res=f[v][i],ans[v]=i;
			}
		}
		if(f[v][ans[u]]-m<f[v][ans[v]]){
			ans[v]=ans[u];
		}
		dfs2(v,u);
	}
}
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<n;i++){
		cin>>a[i];
	}
	memset(dis,0x3f,sizeof dis);
	for(int i=1;i<=n;i++){
		dis[i][i]=0;
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
		dis[u][v]=dis[v][u]=1;
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=n;j++){
//			cout<<dis[i][j]<<" ";
//		}
//		puts("");
//	}
	dfs1(1,0);
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=n;j++){
//			printf("%2d ",f[i][j]);
//		}
//		puts("");
//	}
	int res=inf;
	for(int i=1;i<=n;i++){
		if(res>f[1][i]){
			res=f[1][i],ans[1]=i;
		}
	}
	cout<<res<<"\n";
	dfs2(1,0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
}
}
signed main(){return asbt::main();}

C. 「JZOI-1」旅行

考虑一次询问,显然 DP,设 \(f_{u,0/1}\) 表示走路/坐船到 \(u\) 点的最小花费即可。
多次询问,考虑维护矩阵,广义矩阵乘,倍增处理询问。对每个点 \(u\) 维护 \(up_{u,i}\)\(dw_{u,i}\) 表示从 \(u\) 向上走到 \(2^i\) 级祖先的矩阵和从 \(2^i\) 级祖先向下走到 \(u\) 的矩阵。时间复杂度 \(O((n+t)\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=2e5+5;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,m,q,enm,hd[maxn];
int anc[maxn][22],dep[maxn];
struct edge{
	int v,nxt;
	ll w,d;
	bool typ;
}e[maxn<<1];
il void addedge(int u,int v,ll w,ll d,bool typ){
	e[++enm]=(edge){v,hd[u],w,d,typ};
	hd[u]=enm;
}
struct juz{
	ll mat[2][2];
	juz(){
		mat[0][0]=mat[0][1]=mat[1][0]=mat[1][1]=0;
	}
	il ll*operator[](int x){
		return mat[x];
	}
	il juz operator*(juz x)const{
		juz res;
		res[0][0]=res[0][1]=res[1][0]=res[1][1]=inf;
		for(int i=0;i<=1;i++){
			for(int j=0;j<=1;j++){
				for(int k=0;k<=1;k++){
					res[i][j]=min(res[i][j],mat[i][k]+x[k][j]);
				}
			}
		}
		return res;
	}
}up[maxn][22],dw[maxn][22];
il void dfs(int u,int fa){
	anc[u][0]=fa,dep[u]=dep[fa]+1;
	for(int i=1;i<=20;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
		up[u][i]=up[u][i-1]*up[anc[u][i-1]][i-1];
		dw[u][i]=dw[anc[u][i-1]][i-1]*dw[u][i-1];
	}
	for(int i=hd[u],v,w,d,typ;i;i=e[i].nxt){
		v=e[i].v,w=e[i].w,d=e[i].d,typ=e[i].typ;
		if(v==fa){
			continue;
		}
		up[v][0][0][0]=up[v][0][1][0]=w;
		dw[v][0][0][0]=dw[v][0][1][0]=w;
		if(typ){
			up[v][0][0][1]=m+w+d;
			up[v][0][1][1]=w+d;
			dw[v][0][0][1]=m+w-d;
			dw[v][0][1][1]=w-d;
		}
		else{
			up[v][0][0][1]=m+w-d;
			up[v][0][1][1]=w-d;
			dw[v][0][0][1]=m+w+d;
			dw[v][0][1][1]=w+d;
		}
		dfs(v,u);
	}
}
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,u,v,w,d,typ;i<n;i++){
		cin>>u>>v>>w>>d>>typ;
		addedge(u,v,w,d,typ);
		addedge(v,u,w,d,typ^1);
	}
	dfs(1,0);
//	for(int i=1;i<=n;i++){
//		for(int j=0;j<=1;j++){
//			cout<<up[i][j][0][0]<<" "<<up[i][j][0][1]<<" ";
//		}
//		puts("");
//		for(int j=0;j<=1;j++){
//			cout<<up[i][j][1][0]<<" "<<up[i][j][1][1]<<" ";
//		}
//		puts("");
//	}
//	for(int i=1;i<=n;i++){
//		for(int j=0;j<=1;j++){
//			cout<<dw[i][j][0][0]<<" "<<dw[i][j][0][1]<<" ";
//		}
//		puts("");
//		for(int j=0;j<=1;j++){
//			cout<<dw[i][j][1][0]<<" "<<dw[i][j][1][1]<<" ";
//		}
//		puts("");
//	}
	while(q--){
		int u,v;
		cin>>u>>v;
		if(u==v){
			cout<<"0\n";
			continue;
		}
		juz resu,resv;
		bool flu=0,flv=0;
		if(dep[u]>dep[v]){
			int ddep=dep[u]-dep[v],tmp=0;
			while(ddep){
				if(ddep&1){
					if(!flu){
						resu=up[u][tmp];
						flu=1;
					}
					else{
						resu=resu*up[u][tmp];
					}
					u=anc[u][tmp];
				}
				ddep>>=1,tmp++;
			}
			if(u==v){
				cout<<min(resu[0][0],resu[0][1])<<"\n";
				continue;
			}
		}
		else{
			int ddep=dep[v]-dep[u],tmp=0;
			while(ddep){
				if(ddep&1){
					if(!flv){
						resv=dw[v][tmp];
						flv=1;
					}
					else{
						resv=dw[v][tmp]*resv;
					}
					v=anc[v][tmp];
				}
				ddep>>=1,tmp++;
			}
			if(u==v){
				cout<<min(resv[0][0],resv[0][1])<<"\n";
				continue;
			}
		}
//		for(int i=0;i<=1;i++){
//			for(int j=0;j<=1;j++){
//				cout<<resu[i][j]<<" ";
//			}
//			puts("");
//		}
//		for(int i=0;i<=1;i++){
//			for(int j=0;j<=1;j++){
//				cout<<resv[i][j]<<" ";
//			}
//			puts("");
//		}
		for(int i=20;~i;i--){
			if(anc[u][i]!=anc[v][i]){
				if(!flu){
					resu=up[u][i];
					flu=1;
				}
				else{
					resu=resu*up[u][i];
				}
				if(!flv){
					resv=dw[v][i];
					flv=1;
				}
				else{
					resv=dw[v][i]*resv;
				}
				u=anc[u][i],v=anc[v][i];
			}
		}
		if(!flu){
			resu=up[u][0];
		}
		else{
			resu=resu*up[u][0];
		}
		if(!flv){
			resv=dw[v][0];
		}
		else{
			resv=dw[v][0]*resv;
		}
		juz res=resu*resv;
		cout<<min(res[0][0],res[0][1])<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 「DBOI」Round 1 人生如树

新加的点不会影响之前的询问,所以直接离线,先把所有点都建好。
将问题转化为:用 \(b\) 数组减去 \(a\) 数组,得到的形如 \(1,2,3,\dots\) 的等差序列的最大长度。
考虑将两个序列哈希,预处理出等差数列的哈希值,二分长度即可。而在树上维护路径数组的哈希值,可以用倍增解决。
时间复杂度 \(O(m\log^2n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
const ull bas1=13331;
const ll bas2=13327,mod2=1e9+7;
int n,m,q,T,_2[22];
int a[maxn],dep[maxn];
int anc[maxn][22];
vector<int> e[maxn];
struct wnti{
	int u1,v1,u2,v2;
}wt[maxn];
ull tar1[maxn],pw1[maxn];
ull up1[maxn][22],dw1[maxn][22];
ll tar2[maxn],pw2[maxn];
ll up2[maxn][22],dw2[maxn][22];
il void dfs(int u,int fa){
	anc[u][0]=fa,dep[u]=dep[fa]+1;
	up1[u][0]=dw1[u][0]=a[u]*pw1[1];
	up2[u][0]=dw2[u][0]=a[u]*pw2[1]%mod2;
	for(int i=1;i<=20;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
		up1[u][i]=up1[u][i-1]+up1[anc[u][i-1]][i-1]*pw1[_2[i-1]];
		dw1[u][i]=dw1[u][i-1]*pw1[_2[i-1]]+dw1[anc[u][i-1]][i-1];
		up2[u][i]=(up2[u][i-1]+up2[anc[u][i-1]][i-1]*pw2[_2[i-1]])%mod2;
		dw2[u][i]=(dw2[u][i-1]*pw2[_2[i-1]]+dw2[anc[u][i-1]][i-1])%mod2;
	}
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs(v,u);
	}
}
il int lca(int u,int v){
	if(dep[u]<dep[v]){
		swap(u,v);
	}
	int ddep=dep[u]-dep[v],tmp=0;
	while(ddep){
		if(ddep&1){
			u=anc[u][tmp];
		}
		ddep>>=1,tmp++;
	}
	if(u==v){
		return u;
	}
	for(int i=20;~i;i--){
		if(anc[u][i]!=anc[v][i]){
			u=anc[u][i],v=anc[v][i];
		}
	}
	return anc[u][0];
}
il int ganc(int u,int k){
	int tmp=0;
	while(k){
		if(k&1){
			u=anc[u][tmp];
		}
		k>>=1,tmp++;
	}
	return u;
}
il ull gha1(int u,int v,int rt,int len){
//	cout<<u<<" "<<v<<"\n";
	if(len<=dep[u]-dep[rt]+1){
		int tmp=0,cur=0;
		ull res=0;
		while(len){
			if(len&1){
				res+=up1[u][tmp]*pw1[cur];
				cur+=_2[tmp],u=anc[u][tmp];
			}
			len>>=1,tmp++;
		}
//		cout<<res<<"\n";
		return res;
	}
	int ddep=dep[u]-dep[rt]+1;
	int tmp=0,cur=0;
	ull res=0;
	len-=ddep;
	int tdep=ddep;
	while(ddep){
		if(ddep&1){
			res+=up1[u][tmp]*pw1[cur];
			cur+=_2[tmp],u=anc[u][tmp];
		}
		ddep>>=1,tmp++;
	}
	v=ganc(v,dep[v]-dep[rt]-len);
//	cout<<v<<" "<<res<<"\n";
	tmp=cur=0;
	int tlen=len;
	while(len){
		if(len&1){
			res+=dw1[v][tmp]*pw1[tdep+tlen-cur-_2[tmp]];
			cur+=_2[tmp],v=anc[v][tmp];
		}
		len>>=1,tmp++;
	}
//	cout<<res<<"\n";
	return res;
}
il ll gha2(int u,int v,int rt,int len){
	if(len<=dep[u]-dep[rt]+1){
		int tmp=0,cur=0;
		ll res=0;
		while(len){
			if(len&1){
				(res+=up2[u][tmp]*pw2[cur])%=mod2;
				cur+=_2[tmp],u=anc[u][tmp];
			}
			len>>=1,tmp++;
		}
		return res;
	}
	int ddep=dep[u]-dep[rt]+1;
	int tmp=0,cur=0;
	ll res=0;
	len-=ddep;
	int tdep=ddep;
	while(ddep){
		if(ddep&1){
			(res+=up2[u][tmp]*pw2[cur])%=mod2;
			cur+=_2[tmp],u=anc[u][tmp];
		}
		ddep>>=1,tmp++;
	}
	v=ganc(v,dep[v]-dep[rt]-len);
	tmp=cur=0;
	int tlen=len;
	while(len){
		if(len&1){
			(res+=dw2[v][tmp]*pw2[tdep+tlen-cur-_2[tmp]])%=mod2;
			cur+=_2[tmp],v=anc[v][tmp];
		}
		len>>=1,tmp++;
	}
	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>>T;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	while(m--){
		int opt;
		cin>>opt;
		if(opt==1){
			int u1,v1,u2,v2;
			cin>>u1>>v1>>u2>>v2;
			wt[++q]=(wnti){u1,v1,u2,v2};
		}
		else{
			int u,w;
			cin>>u>>w;
			a[++n]=w;
			e[u].pb(n),e[n].pb(u);
		}
	}
	pw1[0]=pw2[0]=1;
	for(int i=1;i<=n;i++){
		pw1[i]=pw1[i-1]*bas1;
		pw2[i]=pw2[i-1]*bas2%mod2;
		tar1[i]=tar1[i-1]+i*pw1[i];
		tar2[i]=(tar2[i-1]+i*pw2[i])%mod2;
	}
	_2[0]=1;
	for(int i=1;i<=20;i++){
		_2[i]=_2[i-1]<<1;
	}
	dfs(1,0);
//	for(int u=1;u<=n;u++){
//		for(int i=0;i<=2;i++){
//			cout<<anc[u][i]<<" ";
//		}
//		puts("");
//	}
	for(int i=1,u1,v1,u2,v2,rt1,rt2,l,r;i<=q;i++){
		u1=wt[i].u1,v1=wt[i].v1;
		u2=wt[i].u2,v2=wt[i].v2;
		rt1=lca(u1,v1),rt2=lca(u2,v2);
		l=0;
		r=min(dep[u1]+dep[v1]-2*dep[rt1],dep[u2]+dep[v2]-2*dep[rt2])+1;
//		cout<<l<<" "<<r<<"\n";
		while(l<r){
			int mid=(l+r+1)>>1;
//			cout<<l<<" "<<r<<" "<<mid<<"\n";
			if(gha1(u2,v2,rt2,mid)-gha1(u1,v1,rt1,mid)==tar1[mid]&&(gha2(u2,v2,rt2,mid)-gha2(u1,v1,rt1,mid)+mod2)%mod2==tar2[mid]){
				l=mid;
			}
			else{
				r=mid-1;
			}
		}
		cout<<l<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}

E. Mobile Phone Network

首先强制选择那 \(k\) 条边跑最小生成树。然后用非树边限制路径上的边。时间复杂度 \(O(m\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;}
namespace IO{
	const int bufsz=1<<20;
	char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
	#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
	il int read(){
		char ch=getchar();
		while(ch<'0'||ch>'9'){
			ch=getchar();
		}
		int x=0;
		while(ch>='0'&&ch<='9'){
			x=(x<<1)+(x<<3)+(ch^48);
			ch=getchar();
		}
		return x;
	}
	#undef getchar
	char obuf[bufsz],*p3=obuf,s[50];
	#define flush() (fwrite(obuf,1,p3-obuf,stdout),p3=obuf)
	#define putchar(ch) (p3==obuf+bufsz&&flush(),*p3++=(ch))
	il void write(ll x){
		if(x<0){
			putchar('-');
			x=-x;
		}
		int top=0;
		do{
			s[++top]=x%10|48;
			x/=10;
		}while(x);
		while(top){
			putchar(s[top--]);
		}
		putchar('\n');
	}
	#undef putchar
	class Flush{
		public:
		~Flush(){
			flush();
		}
	}FL;
	#undef flush
}
using IO::read;
using IO::write;
const int maxn=5e5+5;
const int inf=0x3f3f3f3f;
int n,k,m,cnt,fa[maxn],sz[maxn];
int hes[maxn],dep[maxn],top[maxn];
int dfn[maxn],idx[maxn],tofa[maxn];
int zhi[maxn<<2];
bool vis[maxn];
vector<pii> e[maxn];
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w<x.w;
	}
}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;
	}
}
il void dfs1(int u){
//	cout<<u<<"\n";
//	puts("666");
	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,tofa[v]=w;
		dep[v]=dep[u]+1;
		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,w=i.sec;
		if(v==fa[u]||v==hes[u]){
			continue;
		}
		dfs2(v);
	}
}
il void build(int id,int l,int r){
	if(l==r){
		zhi[id]=tofa[idx[l]];
		return ;
	}
	zhi[id]=inf;
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
}
il void upd(int id,int L,int R,int l,int r,int v){
	if(l>r){
		return ;
	}
	if(L>=l&&R<=r){
		zhi[id]=min(zhi[id],v);
		return ;
	}
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r,v);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r,v);
	}
}
il int query(int id,int l,int r,int p){
	if(l==r){
		return zhi[id];
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		return min(zhi[id],query(lid,l,mid,p));
	}
	return min(zhi[id],query(rid,mid+1,r,p));
}
il void upd(int u,int v,int w){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]){
			swap(u,v);
		}
		upd(1,1,n,dfn[top[u]],dfn[u],w);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]){
		swap(u,v);
	}
	upd(1,1,n,dfn[u]+1,dfn[v],w);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
//	freopen("E.in","r",stdin);
	n=read(),k=read(),m=read();
	for(int i=1;i<=n;i++){
		fa[i]=i,sz[i]=1;
	}
	for(int i=1,u,v;i<=k;i++){
		u=read(),v=read();
		e[u].pb(mp(v,inf));
		e[v].pb(mp(u,inf));
		merge(u,v);
	}
	for(int i=1;i<=m;i++){
		a[i].u=read(),a[i].v=read(),a[i].w=read();
	}
	sort(a+1,a+m+1);
	for(int i=1,u,v;i<=m;i++){
		u=a[i].u,v=a[i].v;
		if(find(u)!=find(v)){
			merge(u,v);
			vis[i]=1;
			e[u].pb(mp(v,0));
			e[v].pb(mp(u,0));
		}
	}
	for(int i=1;i<=n;i++){
		fa[i]=0;
	}
	dfs1(1),dfs2(1);
//	puts("666");
	build(1,1,n);
	for(int i=1;i<=m;i++){
		if(!vis[i]){
			upd(a[i].u,a[i].v,a[i].w);
		}
	}
	ll ans=0;
	for(int i=1,res;i<=n;i++){
		res=query(1,1,n,i);
		if(res>=inf){
			write(-1);
			return 0;
		}
		ans+=res;
	}
	write(ans);
	return 0;
}
}
signed main(){return asbt::main();}

F. Close Vertices

点分治,对于每个子树中的点按照到重心的边权和排序,用双指针维护边权和的限制,用树状数组维护边数限制。时间复杂度 \(O(n\log^2n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int n,m,k,enm=1,hd[maxn];
int sz[maxn],mxs[maxn];
int dep[maxn],dis[maxn];
bool ban[maxn<<1];
vector<int> curd;
struct edge{
	int v,nxt,w;
}e[maxn<<1];
il void addedge(int u,int v,int w){
	e[++enm]=(edge){v,hd[u],w};
	hd[u]=enm;
}
struct{
	int tr[maxn];
	il int lowbit(int x){
		return x&-x;
	}
	il void upd(int p,int v){
		for(;p<=n+1;p+=lowbit(p)){
			tr[p]+=v;
		}
	}
	il int query(int p){
		int res=0;
		for(;p;p-=lowbit(p)){
			res+=tr[p];
		}
		return res;
	}
}F;
il void dfs1(int u,int fa,int tot){
	sz[u]=1,mxs[u]=0;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(v==fa||ban[i]){
			continue;
		}
		dfs1(v,u,tot);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],tot-sz[u]);
}
il void dfs2(int u,int fa){
	for(int i=hd[u],v,w;i;i=e[i].nxt){
		v=e[i].v,w=e[i].w;
		if(v==fa||ban[i]){
			continue;
		}
		dep[v]=dep[u]+1,dis[v]=dis[u]+w;
		dfs2(v,u);
	}
}
il ll gans(){
	sort(curd.begin(),curd.end(),[](const int x,const int y){return dis[x]<dis[y];});
	for(int u:curd){
		F.upd(dep[u]+1,1);
//		cout<<u<<" "<<dep[u]<<" "<<dis[u]<<"\n";
	}
	int l=0,r=curd.size()-1;
	ll res=0;
	while(l<r){
		if(dis[curd[l]]+dis[curd[r]]<=k){
			F.upd(dep[curd[l]]+1,-1);
			res+=F.query(max(m-dep[curd[l++]]+1,0));
		}
		else{
			F.upd(dep[curd[r--]]+1,-1);
		}
	}
	F.upd(dep[curd[l]]+1,-1);
	return res;
}
il void dfs3(int u,int fa){
	curd.pb(u);
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(v==fa||ban[i]){
			continue;
		}
		dfs3(v,u);
	}
}
il ll solve(){
	if(curd.size()==1){
		return 0;
	}
	dfs1(curd.front(),0,curd.size());
	int rt=0,mmx=inf;
	for(int u:curd){
		if(mmx>mxs[u]){
			mmx=mxs[u],rt=u;
		}
	}
//	cout<<rt<<"\n";
	dep[rt]=dis[rt]=0;
	dfs2(rt,0);
	ll res=0;
	res+=gans();
//	cout<<res<<"\n";
	for(int i=hd[rt],v;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v;
		curd.clear();
		dfs3(v,rt);
		res-=gans();
	}
	for(int i=hd[rt],v;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v;
		ban[i]=ban[i^1]=1;
		curd.clear();
		dfs3(v,0);
		res+=solve();
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
//	cout<<cplx::usdmem();
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>k;
	for(int i=2,u,w;i<=n;i++){
		cin>>u>>w;
		addedge(u,i,w);
		addedge(i,u,w);
	}
	for(int i=1;i<=n;i++){
		curd.pb(i);
	}
	cout<<solve()<<"\n";
	return 0;
}
}
int main(){return asbt::main();}

G. Nastia Plays with a Tree

构造题,从下往上构造,将每个子树都连成一条链。
链分为两种:

  1. 以根为一头的链
  2. 跨过根的链

对于一个节点 \(u\),若它的儿子中没有或只有一个 \(1\) 号链,那就把其它儿子接上去,将 \(u\) 的子树变为 \(1\) 号链;否则就保留 \(2\) 个子树,将 \(u\) 的子树变为 \(2\) 号链。
时间复杂度 \(O(n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int T,n;
vector<int> e[maxn],curd[maxn];
struct{
	int a,b,typ;
}zis[maxn];
struct node{
	int a,b,c,d;
};
vector<node> ans;
il void dfs(int u,int fa){
//	cout<<u<<"\n";
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
//		cout<<u<<" "<<v<<"\n";
		dfs(v,u);
		curd[u].pb(v);
	}
//	puts("");
	zis[u].typ=1,zis[u].a=u;
	sort(curd[u].begin(),curd[u].end(),[](const int &x,const int &y){return zis[x].typ<zis[y].typ;});
	int cnt=0;
	for(int v:curd[u]){
		if(zis[v].typ==1){
			cnt++;
		}
		else{
			break;
		}
	}
//	cout<<u<<" "<<curd.size()<<"\n";
	if(!cnt){
		for(int v:curd[u]){
			ans.pb((node){u,v,zis[u].a,zis[v].a});
			zis[u].a=zis[v].b;
		}
	}
	else if(cnt==1){
		zis[u].a=zis[curd[u][0]].a;
		for(int i=1,v;i<curd[u].size();i++){
			v=curd[u][i];
			ans.pb((node){u,v,zis[u].a,zis[v].a});
			zis[u].a=zis[v].b;
		}
	}else{
		zis[u].typ=2;
		zis[u].a=zis[curd[u][0]].a;
		zis[u].b=zis[curd[u][1]].a;
		for(int i=2,v;i<curd[u].size();i++){
			v=curd[u][i];
			if(zis[v].typ==1){
				ans.pb((node){u,v,zis[u].a,v});
				zis[u].a=zis[v].a;
			}
			else{
				ans.pb((node){u,v,zis[u].a,zis[v].a});
				zis[u].a=zis[v].b;
			}
		}
	}
}
il void solve(){
	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	ans.clear();
	dfs(1,0);
	for(int i=1;i<=n;i++){
//		cout<<i<<" "<<zis[i].typ<<" "<<zis[i].a<<" "<<zis[i].b<<"\n";
	}
	cout<<ans.size()<<"\n";
	for(node i:ans){
		cout<<i.a<<" "<<i.b<<" "<<i.c<<" "<<i.d<<"\n";
	}
	for(int i=1;i<=n;i++){
		e[i].clear();
		curd[i].clear();
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}
}
int main(){return asbt::main();}

H. [ARC165E] Random Isolation

考虑将操作改为任选一个排列 \(p\),从 \(1\)\(n\) 依次操作,如果 \(p_i\) 所在的连通块大小大于 \(k\),那么就删掉,否则就不删。这样的转化是正确的,因为当前 \(i-1\) 个位置相同时,当前可操作的点出现在 \(p_i\) 的概率是相等的。
考虑一个大小为 \(x\) 的连通块,它连向外面的边数为 \(y\),这个局面出现的概率就是 \(\frac{x!y!}{(x+y)!}\)。因此我们需要计数每种局面的数量。
\(f_{u,i,j}\) 表示 \(u\) 的子树内,强制选 \(u\),选了 \(i\) 个点,向外(除了 \(fa_u\))连了 \(j\) 条边的数量。背包转移即可。时间复杂度 \(O(n^4)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=105,mod=998244353;
int n,m,sz[maxn];
int nf[maxn][maxn];
int f[maxn][maxn][maxn];
int fac[maxn<<1],inv[maxn<<1];
vector<int> e[maxn];
il int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		y>>=1,x=x*1ll*x%mod;
	}
	return res;
}
il void dfs(int u,int fa){
	f[u][1][0]=sz[u]=1;
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs(v,u);
		for(int i=0;i<=sz[u]+sz[v];i++){
			for(int j=0;j<=sz[u]+sz[v];j++){
				nf[i][j]=0;
			}
		}
		for(int i=0;i<=sz[u];i++){
			for(int j=0;j<=sz[u];j++){
				for(int x=0;x<=sz[v];x++){
					for(int y=0;y<=sz[v];y++){
						(nf[i+x][j+y]+=f[u][i][j]*1ll*f[v][x][y]%mod)%=mod;
					}
				}
			}
		}
		sz[u]+=sz[v];
		for(int i=0;i<=sz[u];i++){
			for(int j=0;j<=sz[u];j++){
				f[u][i][j]=nf[i][j];
			}
		}
	}
	f[u][0][1]=1;
}
il void init(int x){
	fac[0]=1;
	for(int i=1;i<=x;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
	}
	inv[x]=qpow(fac[x],mod-2);
	for(int i=x;i;i--){
		inv[i-1]=inv[i]*1ll*i%mod;
	}
}
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;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	dfs(1,0),init(n<<1);
//	for(int i=1;i<=n;i++){
//		cout<<fac[i]<<" "<<fac[i]*1ll*inv[i]%mod<<"\n";
//	}
	int ans=0;
	for(int u=1;u<=n;u++){
		for(int i=m+1;i<=sz[u];i++){
			for(int j=0,k;j<=sz[u];j++){
				k=j;
				if(u!=1){
					k++;
				}
				(ans+=f[u][i][j]*1ll*fac[i]%mod*fac[k]%mod*inv[i+k]%mod)%=mod;
			}
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

I. 「LNOI2014」LCA

图论相关问题 B。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=5e4+5,mod=201314;
int n,m,fa[maxn],sz[maxn];
int hes[maxn],top[maxn];
int dfn[maxn],cnt,ans[maxn];
int sum[maxn<<2],tag[maxn<<2];
vector<int> e[maxn];
vector<pii> wt[maxn];
il void dfs1(int u){
	sz[u]=1;
	int mxs=0;
	for(int v:e[u]){
		dfs1(v);
		sz[u]+=sz[v];
		if(mxs<sz[v]){
			mxs=sz[v],hes[u]=v;
		}
	}
}
il void dfs2(int u){
	dfn[u]=++cnt;
	if(!top[u]){
		top[u]=u;
	}
	if(hes[u]){
		top[hes[u]]=top[u];
		dfs2(hes[u]);
	}
	for(int v:e[u]){
		if(v==hes[u]){
			continue;
		}
		dfs2(v);
	}
}
il void pushup(int id){
	sum[id]=sum[lid]+sum[rid];
}
il void pushtag(int id,int l,int r,int v){
	tag[id]+=v;
	sum[id]+=(r-l+1)*v;
}
il void pushdown(int id,int l,int r){
	if(tag[id]){
		int mid=(l+r)>>1;
		pushtag(lid,l,mid,tag[id]);
		pushtag(rid,mid+1,r,tag[id]);
		tag[id]=0;
	}
}
il void upd(int id,int L,int R,int l,int r,int v){
	if(L>=l&&R<=r){
		pushtag(id,L,R,v);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r,v);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r,v);
	}
	pushup(id);
}
il int query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return sum[id];
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1,res=0;
	if(l<=mid){
		res+=query(lid,L,mid,l,r);
	}
	if(r>mid){
		res+=query(rid,mid+1,R,l,r);
	}
	return res;
}
il void upd(int u){
	while(u){
		upd(1,1,n,dfn[top[u]],dfn[u],1);
		u=fa[top[u]];
	}
}
il int query(int u){
	int res=0;
	while(u){
		res+=query(1,1,n,dfn[top[u]],dfn[u]);
		u=fa[top[u]];
	}
	return res;
}
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=2;i<=n;i++){
		cin>>fa[i];
		fa[i]++;
		e[fa[i]].pb(i);
	}
	dfs1(1),dfs2(1);
	for(int i=1,l,r,u;i<=m;i++){
		cin>>l>>r>>u;
		r++,u++;
		wt[l].pb(mp(u,-i));
		wt[r].pb(mp(u,i));
	}
	for(int i=1;i<=n;i++){
		upd(i);
		for(pii j:wt[i]){
			if(j.sec>0){
				ans[j.sec]+=query(j.fir);
			}
			else{
				ans[-j.sec]-=query(j.fir);
			}
		}
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]%mod<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}

J. Tree and Queries

考虑记录每个数量的颜色数,用树状数组查询。
对于每个子树,拍到 dfs 序上后就是一个区间。因此莫队即可。时间复杂度 \(O(n\sqrt{n}\log n)\)

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 fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,a[maxn],ans[maxn];
int sz[maxn],dfn[maxn];
int cnt,idx[maxn],buc[maxn];
int blen,bel[maxn];
vector<int> e[maxn];
vector<pii> wt[maxn];
il void dfs(int u,int fa){
	dfn[u]=++cnt;
	idx[cnt]=a[u];
	sz[u]=1;
	for(int v:e[u]){
		if(v!=fa){
			dfs(v,u);
			sz[u]+=sz[v];
		}
	}
}
struct wnti{
	int l,r,u;
	il bool operator<(const wnti &x)const{
		if(bel[l]==bel[x.l]){
			return bel[l]&1?r>x.r:r<x.r;
		}
		return l<x.l;
	}
}wnt[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;
		}
	}
	il int query(int p){
		int res=0;
		for(;p;p-=lowbit(p)){
			res+=tr[p];
		}
		return res;
	}
}F;
il void add(int x){
	if(buc[x]){
		F.upd(buc[x],-1);
	}
	F.upd(++buc[x],1);
}
il void del(int x){
	F.upd(buc[x]--,-1);
	if(buc[x]){
		F.upd(buc[x],1);
	}
}
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;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	for(int i=1,u,k;i<=m;i++){
		cin>>u>>k;
		if(k>n){
			k=n+1;
		}
		wt[u].pb(mp(k,i));
	}
	dfs(1,0);
//	puts("666");
	for(int i=1;i<=n;i++){
		wnt[i]=(wnti){dfn[i],dfn[i]+sz[i]-1,i};
	}
	blen=sqrt(n);
	for(int i=1;i<=n;i++){
		bel[i]=i/blen;
	}
	sort(wnt+1,wnt+n+1);
	int l=1,r=0;
	for(int i=1;i<=n;i++){
		while(l>wnt[i].l){
			add(idx[--l]);
		}
		while(r<wnt[i].r){
			add(idx[++r]);
		}
		while(l<wnt[i].l){
			del(idx[l++]);
		}
		while(r>wnt[i].r){
			del(idx[r--]);
		}
		for(pii j:wt[wnt[i].u]){
			ans[j.sec]=F.query(n)-F.query(j.fir-1);
		}
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2025-02-08 19:14  zhangxy__hp  阅读(10)  评论(0编辑  收藏  举报