【BZOJ5480】路径的条数(DFS序)(线段树维护扫描线)

本文介绍了一种通过构建DFS序和使用线段树维护矩形并集的方法来解决计数图中不合法路径的问题。利用祖先关系和子树特性,将路径约束转化为矩形,并通过扫描线算法高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门


解析:

考虑统计不合法的路径条数。

i n [ u ] in[u] in[u]表示 d f s dfs dfs序中, u u u的进入时间戳, o u t [ u ] out[u] out[u]表示退出时间戳。

一个方案可以表示为点集的笛卡尔积,由于是无向的,所以我们只考虑 i n [ u ] < i n [ v ] in[u] < in[v] in[u]<in[v]的路径。

一个限制可以表示为 ( a , k a ) (a,ka) (a,ka)的形式,即每条合法路径不能覆盖任何一个 ( a , k a ) (a,ka) (a,ka)

所有限制显然一共只有 O ( n log ⁡ n ) O(n\log n) O(nlogn)个,将所有限制全部提出来。

一个限制 ( u , v ) (u,v) (u,v)的实际影响可以分类讨论。假设 i n [ u ] < i n [ v ] in[u]<in[v] in[u]<in[v]

1. u u u v v v的祖先。
则所有一端点在 v v v的子树内,另一端点在 u u u的子树外或 u u u不含 v v v的子树内部的路径全部都不合法。

u u u的儿子中,子树包含 v v v的为 t t t,则这个限制相当于ban掉了两部分的答案: [ 1 , i n [ t ] − 1 ] × [ i n [ v ] , o u t [ v ] ] [ i n [ v ] , o u t [ v ] ] × [ o u t [ v ] + 1 , n ] [1,in[t]-1] \times [in[v],out[v]] \\ [in[v],out[v]]\times[out[v]+1,n] [1,in[t]1]×[in[v],out[v]][in[v],out[v]]×[out[v]+1,n]

2. u u u v v v之间没有祖先后代关系

则端点分别在 u u u, v v v的子树中的路径不合法:

[ i n [ u ] , o u t [ u ] ] × [ i n [ v ] , o u t [ v ] ] [in[u],out[u]]\times[in[v],out[v]] [in[u],out[u]]×[in[v],out[v]]

我们发现所有限制可以表示成若干的矩形。

所有不合法的限制就是矩形的并集。

上线段树维护扫描线,计算矩形并集就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

using std::cout;
using std::cerr;

cs int N=1e5+5,logN=16;

int n;

std::vector<int> edge[N];

int in[N],out[N],tot;
int fa[N][18],dep[N];

void dfs(int u,int f){
	in[u]=++tot;
	for(int re i=0;fa[u][i];++i)fa[u][i+1]=fa[fa[u][i]][i];
	for(int re e=0,v;e<edge[u].size();++e)if((v=edge[u][e])^f)fa[v][0]=u,dep[v]=dep[u]+1,dfs(v,u);
	out[u]=tot;
}

inline bool isanc(int u,int v){
	return in[u]<in[v]&&in[v]<=out[u];
}

inline int jumpto(int u,int v){
	for(int re i=logN;~i;--i)
	if(dep[fa[v][i]]>dep[u])v=fa[v][i];
	return v;
}

struct node{int u,d,val;};
std::vector<node> vec[N];

inline void rectangle(int x_1,int x_2,int y_1,int y_2){
	vec[x_1].push_back((node){y_2,y_1,1});
	vec[x_2+1].push_back((node){y_2,y_1,-1});
}

inline void work(int u,int v){
	if(in[u]>in[v])std::swap(u,v);
	if(isanc(u,v)){
		int son=jumpto(u,v);
		rectangle(1,in[son]-1,in[v],out[v]);
		rectangle(in[v],out[v],out[son]+1,n);
	}
	else {
		rectangle(in[u],out[u],in[v],out[v]);
	}
}

int full[N<<2],sum[N<<2];

inline int get(int k,int l,int r){
	return full[k]?r-l+1:sum[k];
}

inline void modify(int k,int l,int r,cs int &ql,cs int &qr,cs int &val){
	if(ql<=l&&r<=qr){
		full[k]+=val;
		return ;
	}
	int mid=(l+r)>>1;
	if(ql<=mid)modify(k<<1,l,mid,ql,qr,val);
	if(mid<qr)modify(k<<1|1,mid+1,r,ql,qr,val);
	sum[k]=get(k<<1,l,mid)+get(k<<1|1,mid+1,r);
}

ll ans;

signed main(){
	n=getint();
	for(int re i=1,u,v;i<n;++i){
		u=getint(),v=getint();
		edge[u].push_back(v);
		edge[v].push_back(u); 
	}
	dfs(1,0);
	for(int re i=1;(i<<1)<=n;++i)
	for(int re j=i<<1;j<=n;j+=i)work(i,j);
	ans=(ll)n*(n-1)>>1;
	for(int re i=1;i<n;++i){
		for(int re j=0;j<vec[i].size();++j)modify(1,1,n,vec[i][j].d,vec[i][j].u,vec[i][j].val);
		ans-=get(1,1,n);
	}
	std::cout<<ans<<"\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值