[HNOI2015] 接水果(倍增 + 整体二分)

本文解析了如何通过深度优先搜索处理 Luogu 题目P3242中的路径覆盖问题,介绍了如何将DFS序列转化为矩阵,以及如何通过矩阵操作判断路径覆盖。关键步骤包括确定矩阵边界、矩阵包含查询优化和使用树状数组实现矩阵激活。时间复杂度为O(n log² n)。
摘要由CSDN通过智能技术生成

problem

luogu-P3242

solution

本题的难点在于如何判定路径之间是否覆盖。

这里我们尝试树常见的 dfs \text{dfs} dfs 序。

考虑 x − y x-y xy 路径如果要覆盖 u − v u-v uv 路径需要满足怎样的条件。

以下均假设 d f s ( u ) < d f s ( v ) , d f s ( x ) < d f s ( y ) dfs(u)<dfs(v),dfs(x)<dfs(y) dfs(u)<dfs(v),dfs(x)<dfs(y)

  • l c a ( u , v ) ≠ u lca(u,v)\ne u lca(u,v)=u

    x x x 必须是 u u u 子树内的一点, y y y 必须是 v v v 子树内的一点。

    我们记点 i i i 子树内的 dfs \text{dfs} dfs 序列对应连续区间为 [ l ( i ) , r ( i ) ] [l(i),r(i)] [l(i),r(i)]

    则要 l ( u ) ≤ l ( x ) ≤ r ( u ) ∧ l ( v ) ≤ l ( y ) ≤ r ( v ) l(u)\le l(x)\le r(u)\wedge l(v)\le l(y)\le r(v) l(u)l(x)r(u)l(v)l(y)r(v)

    其实,我们可以将 ( l ( x ) , l ( y ) ) (l(x),l(y)) (l(x),l(y)) 当成一个点的坐标;

    ( l ( u ) , l ( v ) ) − ( r ( u ) , r ( v ) ) (l(u),l(v))-(r(u),r(v)) (l(u),l(v))(r(u),r(v)) 看作左下角为 ( l ( u ) , l ( v ) ) (l(u),l(v)) (l(u),l(v)) 右上角为 ( r ( u ) , r ( v ) ) (r(u),r(v)) (r(u),r(v)) 的矩阵。

    发现这个点就是落在这个矩阵内的。

    所以问题就是某个点被若干个矩形包含,求这里面的权值第 k k k 小的矩阵。

  • l c a ( u , v ) = u lca(u,v)=u lca(u,v)=u

    x x x 必须属于 u → v u\rightarrow v uv 走的第一个点子树外的部分, y y y 仍是 v v v 子树内一点。

    即,假设 u u u v v v 的路径经过的 u u u 的儿子为 w w w,即路径为 u → w → … v u\rightarrow w\rightarrow\dots v uwv

    则要 1 ≤ l ( x ) < l ( w ) ∨ r ( w ) < l ( x ) ≤ n 1\le l(x)<l(w)\vee r(w)<l(x)\le n 1l(x)<l(w)r(w)<l(x)n l ( v ) ≤ l ( y ) ≤ r ( v ) l(v)\le l(y)\le r(v) l(v)l(y)r(v)

    两个条件是独立的。

    • 1 ≤ l ( x ) < l ( w ) 1\le l(x)<l(w) 1l(x)<l(w),对应矩阵 ( 1 , l ( v ) ) − ( l ( w ) − 1 , r ( v ) ) (1,l(v))-(l(w)-1,r(v)) (1,l(v))(l(w)1,r(v))
    • r ( w ) < l ( x ) ≤ n r(w)<l(x)\le n r(w)<l(x)n,对应矩阵 ( l ( v ) , r ( w ) + 1 ) − ( r ( v ) , n ) (l(v),r(w)+1)-(r(v),n) (l(v),r(w)+1)(r(v),n)

l c a lca lca 和某个点的下面一个点,可以倍增,可以树链剖分,好像是链剖分快点,但我们不需要卡这么点常。

对于一个询问,我们可以二分答案,然后把所有权值不大于答案的矩阵激活,统计包含该询问对应点的被激活的矩阵有多少个,根据和 k k k 的关系移动二分端点。

所以我们可以对所有询问套一个整体二分。

而这个矩阵激活我们就可以扫描线地做。

对于 ( x 1 , y 1 ) − ( x 2 , y 2 ) (x1,y1)-(x2,y2) (x1,y1)(x2,y2) 的矩阵,我们以 x x x 轴做扫描线从左往右扫。

变成 ( y 1 , y 2 , x 1 , 1 ) (y1,y2,x1,1) (y1,y2,x1,1) x = x 1 x=x1 x=x1 的时候把 [ y 1 , y 2 ] + 1 [y1,y2]+1 [y1,y2]+1 ( y 1 , y 2 , x 2 + 1 , − 1 ) (y1,y2,x2+1,-1) (y1,y2,x2+1,1) x = x 2 x=x2 x=x2 的时候把 [ y 1 , y 2 ] − 1 [y1,y2]-1 [y1,y2]1

数据结构仍然使用树状数组。

由于使用的是扫描线,所以一开始要将所有询问按 x x x 排序,并且将矩阵按权值排序。

且每次整体二分内部都要将权值属于 [ l , m i d ] [l,mid] [l,mid] 区间的矩阵重新按 x x x 轴排序。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

code

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 40005

vector < int > G[maxn];
int dep[maxn], st[maxn], ed[maxn];
int f[maxn][16];
int cnt;

void dfs( int u, int fa ) {
	dep[u] = dep[fa] + 1, f[u][0] = fa, st[u] = ++ cnt;
	for( int i = 1;i < 16;i ++ ) f[u][i] = f[f[u][i - 1]][i - 1];
	for( int v : G[u] ) if( v ^ fa ) dfs( v, u );
	ed[u] = cnt;
}
int lca( int u, int v ) {
	if( dep[u] < dep[v] ) swap( u, v );
	for( int i = 15;~ i;i -- ) if( dep[f[u][i]] >= dep[v] ) u = f[u][i];
	if( u == v ) return u;
	for( int i = 15;~ i;i -- ) if( f[u][i] ^ f[v][i] ) u = f[u][i], v = f[v][i];
	return f[u][0];
}
int top( int u, int d ) {
	for( int i = 15;~ i;i -- ) if( d >> i & 1 ) u = f[u][i]; return u;
}

int cntg, n, m1, m2;
int ans[maxn];
struct scan { int l, r, x, op; }s[maxn << 2];
struct matrix { int x1, y1, x2, y2, w; }g[maxn << 2];
struct query { int x, y, k, id; }q[maxn], L[maxn], R[maxn];

namespace BIT {
	int t[maxn];
	void add( int l, int r, int k ) {
		for( ;l <= n;l += l & -l ) t[l] += k;
		for( ;r <= n;r += r & -r ) t[r] -= k;
	}
	int ask( int x ) {
		int sum = 0;
		for( ;x;x -= x & -x ) sum += t[x];
		return sum;
	}
}

void solve( int l, int r, int ql, int qr ) {
	if( ql > qr ) return;
	if( l == r ) { for( int i = ql;i <= qr;i ++ ) ans[q[i].id] = g[l].w; return; }
	int mid = l + r >> 1; cnt = 0;
	for( int i = l;i <= mid;i ++ ) {
		s[++ cnt] = (scan){ g[i].y1, g[i].y2, g[i].x1, 1 };
		s[++ cnt] = (scan){ g[i].y1, g[i].y2, g[i].x2 + 1, -1 };
	}
	sort( s + 1, s + cnt + 1, []( scan a, scan b ) { return a.x < b.x; } );
	int cntl = 0, cntr = 0, j = 1;
	for( int i = ql;i <= qr;i ++ ) {
		for( ;j <= cnt and s[j].x <= q[i].x;j ++ ) BIT :: add( s[j].l, s[j].r + 1, s[j].op );
		int k = BIT :: ask( q[i].y );
		if( q[i].k <= k ) L[++ cntl] = q[i];
		else q[i].k -= k, R[++ cntr] = q[i]; 
	}
	for( int i = 1;i < j;i ++ ) BIT :: add( s[i].l, s[i].r + 1, -s[i].op );//不一定把l~mid的矩阵都挂在了树上的
	for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = L[i];
	for( int i = 1;i <= cntr;i ++ ) q[ql + cntl + i - 1] = R[i];
	solve( l, mid, ql, ql + cntl - 1 );
	solve( mid + 1, r, ql + cntl, qr );
}

int main() {
	scanf( "%d %d %d", &n, &m1, &m2 );
	for( int i = 1, u, v;i < n;i ++ ) {
		scanf( "%d %d", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );
	}
	dfs( 1, 0 );
	for( int i = 1, u, v, w;i <= m1;i ++ ) {
		scanf( "%d %d %d", &u, &v, &w );
		if( st[u] > st[v] ) swap( u, v );
		int x = lca( u, v );
		if( x ^ u ) g[++ cntg] = (matrix){ st[u], st[v], ed[u], ed[v], w };
		else {
			x = top( v, dep[v] - dep[u] - 1 );
			if( st[x] ^ 1 ) g[++ cntg] = (matrix){ 1, st[v], st[x] - 1, ed[v], w };
			if( ed[x] ^ n ) g[++ cntg] = (matrix){ st[v], ed[x] + 1, ed[v], n, w };
		}
	}
	for( int i = 1, u, v, k;i <= m2;i ++ ) {
		scanf( "%d %d %d", &u, &v, &k );
		if( st[u] > st[v] ) swap( u, v );
		q[i] = (query){ st[u], st[v], k, i };
	}
	sort( q + 1, q + m2 + 1, [](query a, query b) { return a.x < b.x; } );
	sort( g + 1, g + cntg + 1, [](matrix a, matrix b){ return a.w < b.w; } );
	solve( 1, cntg, 1, m2 );
	for( int i = 1;i <= m2;i ++ ) printf( "%d\n", ans[i] );
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值