[CTSC2016]时空旅行

· · 题解

题意:在一棵树上,每个节点代表一个集合,一些元素存在这个集合之中,每个

节点上的集合,是由父亲的先复制下来,然后修改1个元素,成为一个新的集合。

每个元素有(x,y,z,c)四个值,其实可以发现(y,z)没用,那么,就是两个(x,c)

每次给出树上一个点,以及一个X,要求出这个节点所有元素的

min( ( X - x_i)^2+C_i )

容易想到的是,我们用线段树维护这棵树的dfs序。于是,每个元素就存在

一些区间之中。于是我们可以先找到每个元素存在的区间,然后将这个元素存入

线段树所对应的区间之中。

接着我们可以推柿子了(精通斜率优化或者凸包的可以忽略)

我们令

(X-x_i)^2+C_i=X^2-2Xx_i+x_i^2+C_i=k

那么

2Xx_i+k=X^2+x_i^2+C_i

我们可以把每个(x_i,X^2+x_i^2+C_i)看作是个决策点,而我们有一个斜率为

让$k$尽量的小,也就是到$y$轴的截距。于是我们可以发现,对于每个节点,只需 要维护出一个凸包,然后对于一个$X$,找到斜率第一个大于$2X$的线段,它前面 的点既是最优决策。 但是这样空间开销很大。 于是我们对于$x_i$,从小到大的插入到每一个区间之中,在所对应的线段树的区 间上维护一个凸包。对于询问也对于$X$从小到达的去询问,找到对应的一个点, 并在经过的区间之中,求出最右解并取$min$. 并附上人帅但常数大的代码: ```cpp #include<bits/stdc++.h> using namespace std ; #define N 500007 #define ls (x<<1) #define rs (x<<1|1) #define inf 1e17 #define LL long long int n , m , tot , cnt , num ; int nex[2*N] , fire[N] , to[2*N] ; int L[4*N] , R[4*N] , a[N] , dfn[N] , To[N] ; LL C[N] , Ans[N] , val[N] ; struct node{ LL x , val , opt ; }RR[N] ; bool operator < (node a , node b){ return a.val < b.val ;} bool cmp(int a , int b){ // 求出一个排列,使得val递增 return val[a] < val[b] ; } double kk(int x , int y){ // 求斜率 return (double)( (double)val[x] * val[x] + (double)C[x] - (double)val[y] * val[y] - (double)C[y] ) / (double)( (double)val[x] - (double)val[y] ) ; } void add(int u , int v){ nex[++tot] = fire[u] ; fire[u] = tot ; to[tot] = v ; return ; } vector<int> AL[N] , AR[N] ; vector<int> W[4*N] ; void dfs(int x , int fr){ // 找到每一个包含的区间 dfn[x] = ++num ; if( To[x] > 0 ) AL[To[x]].push_back( num ) ; // 从这个开始一定是个区间左端点 if( To[x] < 0 ) AR[abs(To[x])].push_back( num - 1 ) ; // 从这个结束是个区间右端点 for(int i = fire[x] ; i ; i = nex[i] ){ int v = to[i] ; if( v == fr ) continue ; dfs( v , x ) ; } if( To[x] > 0 ) AR[To[x]].push_back( num ) ; //这个点结束则是个区间右端点 if( To[x] < 0 ) AL[abs(To[x])].push_back( num + 1 ) ; // 这个后面的点一定是个区间左端点 } void build(int x , int l , int r){ L[x] = 0 , R[x] = -1 ; // 这是一个初始化,我就是这个地方调了比较久。 if( l == r ) return ; int mid = ( l + r ) >> 1 ; build( ls , l , mid ) ; build( rs , mid + 1 , r ) ; } void update(int x , int l , int r , int ll , int rr , int t ){ if( ll == l && r == rr ){ while( W[x].size() <= R[x] + 5 ) W[x].push_back( 0 ) ; // 保险一点。 if( L[x] <= R[x] && val[W[x][R[x]]] == val[t] ){ // 可能存在x相等的。 if( C[W[x][R[x]]] <= C[t] ) return ; R[x]-- ; } while( L[x] < R[x] && kk( W[x][R[x]] , t ) < kk( W[x][R[x]] , W[x][R[x] - 1] ) ) R[x]-- ;// 类似于斜率优化的维护凸包。 W[x][R[x] + 1] = t ; R[x]++ ; return ; } int mid = ( l + r ) >> 1 ; if( rr <= mid ) update( ls , l , mid , ll , rr , t ) ; else if( ll > mid ) update( rs , mid + 1 , r , ll , rr , t ) ; else update( ls , l , mid , ll , mid , t ) , update( rs , mid + 1 , r , mid + 1 , rr , t ) ; } LL query(int x , int l , int r , int pos , LL t , LL res ){ LL res1 = inf ; while( L[x] < R[x] && kk( W[x][L[x]] , W[x][L[x] + 1] ) <= 2.0 * (double)t ) L[x]++ ; //对于每一个区间都维护求一下。 if( L[x] <= R[x] && W[x].size() > 0 ) res1 = 1ll * ( t - val[W[x][L[x]]] ) * ( t - val[W[x][L[x]]] ) + C[W[x][L[x]]] ; res1 = min( res , res1 ) ; if( l == r ) return res1 ; int mid = ( l + r ) >> 1 ; if( pos <= mid ) return query( ls , l , mid , pos , t , res1 ) ; else return query( rs , mid + 1 , r , pos , t , res1 ) ; } signed main() { scanf("%d%d%lld" , &n , &m , &C[0] ) ; for(int i = 1 ; i <= n - 1 ; i++ ){ int opt , u , y , z ; LL x ; scanf("%d" , &opt ) ; if( opt == 0 ){ scanf("%d%lld" , &u , &x ) ; scanf("%lld%d%d%lld" , &val[x] , &y , &z , &C[x] ) ; To[i] = x ; add( u , i ) ; add( i , u ) ; } else{ scanf("%d%lld" , &u , &x ) ; To[i] = -x ; add( u , i ) ; add( i , u ) ; } } dfs( 0 , 0 ) ; // dfs序,找出每个区间 build( 1 , 1 , n ) ; for(int j = 1 ; j <= n ; j++ ) a[j] = j ; sort( a + 1 , a + n + 1 , cmp ) ; // 找出哪个val递增的排列 update( 1 , 1 , n , 1 , n , 0 ) ; // 地球哪个要加上 for(int k = 1 ; k <= n ; k++ ){ int i = a[k] ; for(int j = 0 ; j < AL[i].size() ; j++ ){ int L = AL[i][j] , R = AR[i][j] ; if( L <= R ) update( 1 , 1 , n , L , R , i ) ; } } for(int i = 1 ; i <= m ; i++ ){ scanf("%lld%lld" , &RR[i].x , &RR[i].val ) ; RR[i].opt = i ; } sort( RR + 1 , RR + m + 1 ) ; // 询问按从小到大的顺序,可以使得每次操作可是直接取上一次凸包最前面的值。 for(int i = 1 ; i <= m ; i++ ){ Ans[RR[i].opt] = query( 1 , 1 , n , dfn[RR[i].x] , RR[i].val , inf ) ; } for(int i = 1 ; i <= m ; i++ ) printf("%lld\n" , Ans[i] ) ; return 0 ; } /* 4 4 2 0 0 1 10 2 3 7 0 1 2 8 1 6 2 1 1 1 1 4 2 8 2 6 3 8 */ ```