[湖南集训]更为厉害/谈笑风生

题目

  点这里看题目。

分析

  首先发现,对于\((a,b,c)\)的合法三元组,\(c\)一定在\(a\)的子树内,并且\(b\)也是\(c\)的祖先。那么我们只需要考虑\(b\)的位置。如果\(b\)\(a\)的子孙,那么\(c\)一定就是\(b\)的子孙,此时的贡献是\(siz(b)-1\)(我们以下都用\(siz(u)\)表示\(u\)的子树大小)。如果\(b\)\(a\)的祖先,那么贡献就是\(siz(a)-1\)
  发现,当\(b\)\(a\)的祖先的时候,贡献只与\(k\)\(siz(a)\)\(a\)的深度有关,因此可以直接计算。
  因此我们只需要考虑,对每个询问求出:

\[\sum_{dis(p,v)\le k} (siz(v)-1) \]

  这是一个长链剖分可做的问题。但是由于我很蒻,因此我不会。
  考虑到 " \(a\)\(b\)距离不超过\(k\) " 的限制,我们可以发现,\(b\)\(a\)的深度差不会超过\(k\)。因此,知道了\(a\)的深度和\(k\),我们就可以知道合法的\(b\)的最大的深度,我们称这个最大深度为一个询问的 " 可用深度 " 。
  因此,我们将所有点按照深度从小到大,插入贡献。对于一个询问,当深度小于等于它的 " 可用深度 " 的点都被插入后,我们就可以计算它此时的答案——也就是它子树内的贡献和。
  可以发现,此时查询既满足贡献不遗漏,也满足不会有多余的贡献。
  子树内的贡献和,可以通过将树展开为 DFS 序的方法,转化为区间求和,用 BIT 可以维护。点和询问都可以在排序后使用指针扫过去。
  时间复杂度\(O((n+q)\log_2n)\)

代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;

const int MAXN = 3e5 + 5;

template<typename _T>
void read( _T &x )
{
	x = 0;char s = getchar();int f = 1;
	while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
	while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
	x *= f;
}

template<typename _T>
void write( _T x )
{
	if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
	if( 9 < x ){ write( x / 10 ); }
	putchar( x % 10 + '0' );
}

template<typename _T>
_T MIN( const _T a, const _T b )
{
	return a < b ? a : b;
}

template<typename _T>
_T MAX( const _T a, const _T b )
{
	return a > b ? a : b;
}

struct edge
{
	int to, nxt;
}Graph[MAXN << 1];

struct Query
{
	int dd, id, A, K;
	bool operator < ( const Query &b ) const { return dd < b.dd; }
};

Query q[MAXN];

LL BIT[MAXN], ans[MAXN];
int pts[MAXN];
int head[MAXN], dep[MAXN], pos[MAXN], siz[MAXN];
int N, Q, cnt, ID;

int lowbit( const int x ) { return x & ( -x ); }
void update( int x, int v ) { for( ; x <= N ; x += lowbit( x ) ) BIT[x] += v; }
LL getSum( int x ) { LL ret = 0; while( x ) ret += BIT[x], x -= lowbit( x ); return ret; }
LL query( int l, int r ) { return getSum( r ) - getSum( l - 1 ); }
bool cmp( const int &x, const int &y ) { return dep[x] < dep[y]; }

void addEdge( const int from, const int to )
{
	Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
	head[from] = cnt;
}

void DFS( const int u, const int fa )
{
	dep[u] = dep[fa] + 1, siz[u] = 1, pos[u] = ++ ID;
	for( int i = head[u], v ; i ; i = Graph[i].nxt )
		if( ( v = Graph[i].to ) ^ fa )
			DFS( v, u ), siz[u] += siz[v];
}

signed main()
{
	int height = 0;
	read( N ), read( Q );
	for( int i = 1, a, b ; i < N ; i ++ ) read( a ), read( b ), addEdge( a, b ), addEdge( b, a );
	DFS( 1, 0 );
	for( int i = 1 ; i <= N ; i ++ ) height = MAX( height, dep[i] ), pts[i] = i;
	for( int i = 1 ; i <= Q ; i ++ ) 
		read( q[i].A ), read( q[i].K ), q[i].dd = MIN( dep[q[i].A] + q[i].K, height ), q[i].id = i;
	std :: sort( q + 1, q + 1 + Q );
	std :: sort( pts + 1, pts + 1 + N, cmp );
	int rig = 1, ptr = 1;
	for( int i = 1 ; i <= N ; )
	{
		for( ; rig <= N && dep[pts[rig]] == dep[pts[i]] ; rig ++ );
		for( ; i < rig ; i ++ ) update( pos[pts[i]], siz[pts[i]] - 1 );
		if( q[ptr].dd == dep[pts[i - 1]] )
		{
			for( ; ptr <= Q && q[ptr].dd == dep[pts[i - 1]] ; ptr ++ )
			{
				ans[q[ptr].id] = query( pos[q[ptr].A] + 1, pos[q[ptr].A] + siz[q[ptr].A] - 1 );
				ans[q[ptr].id] += 1ll * MIN( q[ptr].K, dep[q[ptr].A] - 1 ) * ( siz[q[ptr].A] - 1 );
			}
		}
	}
	for( int i = 1 ; i <= Q ; i ++ ) write( ans[i] ), putchar( '\n' );
	return 0;
}
posted @ 2020-05-14 13:57  crashed  阅读(111)  评论(0编辑  收藏  举报