nssl 1459 空间简单度

D e s c r i p t i o n Description Description

给定一棵大小为 n n n的树,规定编号为 i i i的点不能去到编号处于 [ i − k , i + k ] [i-k,i+k] [ik,i+k]之间, 求所有满足条件的路径数

数据范围: n ≤ 3 × 1 0 6 , k ≤ 10 n\leq 3\times 10^6,k\leq 10 n3×106,k10


S o l u t i o n Solution Solution

我们发现 k k k很小,所以可以把所有的限制点对都求出来,然后就变成了树这道题了

这里再讲一遍,假如规定 x x x不能到 y y y

  1. x , y x,y x,y没有祖宗关系时,它们的子树不能相互到达(即蓝色部分不能去绿色部分nssl 1459 空间简单度_第1张图片
  2. x , y x,y x,y有祖宗关系(这里强令 x x x y y y的祖宗),则 x x x的祖宗及其兄弟以及自己(蓝色区域)不能去到 y y y的子树(绿色区域)
    nssl 1459 空间简单度_第2张图片

第二种情况蓝色区域的求法:求出 y y y的祖宗中,深度最小的 x x x的儿子,换言之就是 y y y所在这条链上 x x x的儿子,我们记为 z z z,则蓝色区域即为 d f s dfs dfs 1 ∼ d f n [ z ] − 1 1\sim dfn[z]-1 1dfn[z]1的所有节点

这样我们就得到了若干组容斥关系,如果有一个容斥关系 ( l 1 , r 1 ) (l_1,r_1) (l1,r1)不能去到 ( l 2 , r 2 ) (l_2,r_2) (l2,r2)

这说明对于任意的 x ∈ [ l 1 , r 1 ] x\in[l_1,r_1] x[l1,r1]都不能去到 y ∈ [ l 2 , r 2 ] y\in[l_2,r_2] y[l2,r2],如果我们把这个 x , y x,y x,y分别看成横纵坐标则

任意一个横坐标为 ( l 1 ∼ r 1 ) (l_1\sim r_1) (l1r1),纵坐标为 ( l 2 ∼ r 2 ) (l_2\sim r_2) (l2r2)都是不合法的点

注意到这些点恰好围成了一个矩形,左下角坐标为 ( l 1 , l 2 ) (l_1,l_2) (l1,l2),右上角坐标为 ( r 1 , r n ) (r_1,r_n) (r1,rn)
而且不合法的整点(横纵坐标均为整数的点)的个数恰好就是这个矩形的面积

于是,对于所有的限制条件,我们都可以把它看做一个矩形,而不合法的个数,就刚好是矩形的面积并 r e s res res

求出了不合法的个数,用总路径数减去不合法的个数即为答案,即 C n 2 + n − r e s C_{n}^2+n-res Cn2+nres + n +n +n是因为在该题中,自己到自己也算一条路径)

扫描线+线段树就可以解决这个问题,不熟悉这个算法的人可以自行去洛谷搜模板或者做一下Atlantis

时间复杂度: O ( n k   l o g   ( n k ) ) O(nk\ log\ ( nk)) O(nk log (nk))


C o d e Code Code

**#pragma GCC optimize(2) 
#include
#include
#include
#include
#define N 3000010
using namespace std;int n,k,cnt,dfn[N],rfn[N],ed[N],dep[N],f[N][21];
inline long long read()
{
	long long f=0,d=1;char c; 
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
struct xds//线段树
{
	#define lson k<<1
	#define rson k<<1|1
	int dat[N<<2],lzy[N<<2];
	inline void up(int k,int l,int r)
	{
		if(lzy[k]) dat[k]=r-l+1;
		else if(l==r) dat[k]=0;else
		dat[k]=dat[lson]+dat[rson];
		return;
	}
	inline void modify(int ql,int qr,int d,int k=1,int l=1,int r=n)
	{
		if(ql<=l&&r<=qr)
		{
			lzy[k]+=d;
			up(k,l,r);
			return;
		}
		int mid=l+r>>1;
		if(ql<=mid) modify(ql,qr,d,lson,l,mid);
		if(qr>mid) modify(ql,qr,d,rson,mid+1,r);
		up(k,l,r);
		return;
	}
	#undef lson
	#undef rson
}T;
struct tl//树的部分,同时用倍增来求蓝色区域
{
	int l[N],tot;
	struct node
	{
		int next,to;
	}e[N<<1];
	inline void add(int u,int v){e[++tot]=(node){l[u],v};l[u]=tot;return;}
	inline void dfs(int x)
	{
		dfn[++cnt]=x;rfn[x]=cnt;
		for(register int i=l[x];i;i=e[i].next)
		{
			int y=e[i].to;
			if(rfn[y]) continue;
			f[y][0]=x;dep[y]=dep[x]+1;
			dfs(y);
		}
		ed[x]=cnt;
		return;
	}
	inline int LCA(int x,int y)
	{
		for(register int i=20;i>=0;i--) if(dep[f[y][i]]>dep[x]) y=f[y][i];
		return y;
	}
}GT;
struct node{int lie,l,r,d;}sec[N<<2];
int tot;
inline void add(int x1,int x2,int y1,int y2)
{
	sec[++tot]=(node){x1,y1,y2,1};
	sec[++tot]=(node){x2+1,y1,y2,-1};
	return;
}
inline bool cmp(node x,node y){return x.lie<y.lie;}
long long ans;
inline void Ban(int x,int y)//表示x不能到y的处理
{
	if(rfn[x]>rfn[y]) swap(x,y);
	if(rfn[y]<=ed[x]&&rfn[y]>rfn[x])
	{
		int son=GT.LCA(x,y);
		if(rfn[son]>1) add(1,rfn[son]-1,rfn[y],ed[y]);
		if(ed[son]<n) add(rfn[y],ed[y],ed[son]+1,n);
	}
	else add(rfn[x],ed[x],rfn[y],ed[y]);
}
signed main()
{
	int size = 256 << 20; //250M
    char*p=(char*)malloc(size) + size;
    __asm__("movl %0, %%esp\n" :: "r"(p) );
	n=read();k=read();
	for(register int i=1,x,y;i<n;i++) x=read(),y=read(),GT.add(x,y),GT.add(y,x);
	GT.dfs(1);
	for(register int j=1;j<=20;j++)
	 for(register int i=1;i<=n;i++)
	  f[i][j]=f[f[i][j-1]][j-1];
	for(register int i=1;i<=n;i++)
	 for(register int j=i+1;j<=i+k;j++)
	  Ban(i,j);
	sort(sec+1,sec+1+tot,cmp);
 	for(register int i=1,j=1;i<=n;i++)
	{
		for(;j<=tot&&sec[j].lie<=i;j++)
		T.modify(sec[j].l,sec[j].r,sec[j].d);
		ans+=T.dat[1];
	}
	printf("%lld\n",(long long)n*(n-1)/2+n-ans);
}

你可能感兴趣的:(线段树,扫描线)