线段树(二)

http://andyzh1314.ycool.com/post.1475864.html

这篇难读加大,讲述难免不够详细。
建议反复思考,多看程序,会有遁悟之欣喜感觉。

线段树的优化:

pku3017 Cut the Sequence

可以很容易写出规划的模型:F[i] = min ( F [k] + max ( a j | k < j <= i )  | sum { aj | k < j <= i } <= M )
O(n^2)的复杂度是过不了的。
下面我们来用线段树对其进行优化。

先来看数据结构:
struct node_T {
  int64 MinF , TotF;
  int MaxA;
  node_T * Left , * Right;
  void Create ( int , int );
  void Change_F ( int , int , int );
  void Change_A ( int , int , const int & );
  int64 Get_Value ( int , int );
};

这里我们假设当前结点表示的是线段[l,r],现在对每个元素进行解说。
MinF 表示线段[l,r]上最小的F值,即min { F [i] | l <= i <= r }
MaxA 如果是叶结点,表示的是DP方程中的max ( a j | k < j <= i ) 这个部分。
   如果是其他结点,则有两种情况
   -1 : 情况未定;
   其他:整条线段的叶子结点的所有MaxA值均为此MaxA。
TotF 如果MaxA != -1 TotF = MinF + TotF;
   否则TotF = min ( Left->TotF, Right->TotF );

再来看结构中的方法。
Create 自然是构造函数。
Change_F 是修改F值。
Change_A 表示修改线段[S,T]内的部分。

这个复杂度为O(n log n )。
程序见附录1

线段树是解决一维查找问题的有效手段。
对于平面查找问题来说,可以将其转化成kd树。
所谓kd树,就是线段树中的每个结点都是一棵更小的线段树。
这样的效率维护在O(log^2n)
一般平面查找问题,还可以用分散叠层的思想。
即是说线段树的每个结点存储子树的所有结点,按照第二键值排序。
这样的复杂度仍然在O(log^2n)内。
但是我们可以通过指针的运用使得时间复杂度降了一个log阶
具体的办法就是增开一维相对于左右子树第二键值的指针。
这样再来查找,对于上下转移达到了O(1)。
对于树根,二分出范围,层数是log(n),所以整体的复杂度为O(logn)。

附录1
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define maxn 101000

using namespace std;

typedef long long int64;

const int64 Inf = 9999999999999999LL;

struct node_T {
  int64 MinF , TotF;
  int MaxA;
  node_T * Left , * Right;
  void Create ( int , int );
  void Change_F ( int , int , int );
  void Change_A ( int , int , const int & );
  int64 Get_Value ( int , int );
};

int MinP , MaxP , T , S;
int Pred [maxn] , SS , Stack [maxn];
int n , A [maxn];
int64 sum [maxn] , M , F [maxn];
int TreeSize;
node_T Node [maxn * 2 + 2] , * Root = Node;

void init ();
void prepare ();
void solve ();

int main ()
{
  init ();
  prepare ();
  solve ();
}

void solve ()
{
  for ( int i = 1; i <= n; i ++ ) if ( A [i] > M ) {
    printf ( "-1\n" );
    return;
  }
  memset ( Pred , 0 , sizeof ( Pred ));
  SS = 0;
  for ( int i = n; i >= 1; i -- ) {
    for ( ; SS && A [i] >= A [Stack [SS - 1]]; SS -- ) Pred [Stack [SS - 1]] = i;
    Stack [SS ++] = i;
  }

  F [0] = 0 , S = 0;
  for ( int i = 1; i <= n; i ++ ) {
    for ( T = i - 1; sum [i] - sum [S] > M; S ++ );
    Root->Change_F ( MinP , MaxP , i - 1 );

    SS = S , S = Pred [i];
    if ( S <= T ) Root->Change_A ( MinP , MaxP , A [i] );
    S = SS;

    F [i] = Root->Get_Value ( MinP , MaxP );
  }
  cout << F [n] << endl;
}

void prepare ()
{
  MinP = 0 , MaxP = n , TreeSize = 1 , Root->Create ( MinP , MaxP );
}

void init ()
{
  scanf ( "%d" , &n );
  cin >> M;
  sum [0] = 0;
  for ( int i = 1; i <= n; i ++ ) scanf ( "%d" , A + i ) , sum [i] = sum [i - 1] + A [i];
}

int64 node_T :: Get_Value ( int l , int r )
{
  if ( MaxA != -1 ) TotF = MaxA + MinF;
  if ( S <= l && r <= T ) return TotF;
  if ( r < S || T < l ) return Inf;
  int mid = ( l + r ) / 2;
  if ( MaxA != -1 ) Left->MaxA = Right->MaxA = MaxA;
  return min ( Left->Get_Value ( l , mid ) , Right->Get_Value ( mid + 1 , r ));
}

void node_T :: Change_A ( int l , int r , const int & Value )
{
  if ( MaxA >= Value ) return;
  if ( S <= l && r <= T ) { MaxA = Value; TotF = MaxA + MinF; return; }
  if ( r < S || T < l ) return;
  if ( l == r ) { MaxA >?= Value; TotF = MaxA + MinF; return; }

  if ( MaxA != -1 ) Left->MaxA = Right->MaxA = MaxA;
  int mid = ( l + r ) / 2;
  Left->Change_A ( l , mid , Value ) , Right->Change_A ( mid + 1 , r , Value );
  MaxA = ( Left->MaxA == Right->MaxA ? Left->MaxA : -1 );

  if ( MaxA != -1 ) TotF = MinF + MaxA;
  else TotF = min ( Left->TotF , Right->TotF );
}

void node_T :: Change_F ( int l , int r , int p )
{
  if ( l == r ) { MinF = F [p]; TotF = MinF + MaxA; return; }
  int mid = ( l + r ) / 2;

  if ( MaxA != -1 ) Left->MaxA = Right->MaxA = MaxA;
  if ( p <= mid ) Left->Change_F ( l , mid , p );
  else Right->Change_F ( mid + 1 , r , p );
  MinF = min ( Left->MinF , Right->MinF );
  if ( MaxA != -1 ) TotF = MinF + MaxA;
  else TotF = min ( Left->TotF , Right->TotF );
}

void node_T :: Create ( int l , int r )
{
  MinF = Inf , MaxA = -1 , TotF = Inf;
  if ( l == r ) { Left = Right = NULL; return; }
  int mid = ( l + r ) / 2;
  Left = Root + TreeSize ++ , Right = Root + TreeSize ++;
  Left->Create ( l , mid ) , Right->Create ( mid + 1 , r );
}


posted @ 2008-12-04 12:42  jesonpeng  阅读(180)  评论(0编辑  收藏  举报