线段树小结

按照http://www.notonlysuccess.com/牛的博客写了几道题,刷了一周效果不错
1.复习巩固了以前的知识

2.还有改正了一些写线段树毛病

3.改正了在弱数据的影响下的错误方法AC的题目

4.还学到了线段树新的类型题

总体效果不错,总结一下

HDU 1166 赤裸裸的线段树,什么都不用考虑,新手练习用
HDU 1754 依然赤裸裸……一样是单点的操作,求和改为最值,新手练习专用
HDU 1698 基础的区间操作,我每个节点开了以delta域一个sum域,然后1次查询,注意要有延迟操作
HDU 1394 由于序列的特殊性只有0 - n - 1 切每个出现1次,所以每次把头的数移到尾部 都有 ans = ans  - a[i] + (n - a[i] - 1);
    树状数组比线段树    更容易
POJ 2777 很早之前做的了,忘记了,我最初的线段数之一
POJ 3468 注意用long long 区间加减一个操作把 HDU2698 =换成 +=就OK了
PKU 2528 离散化+线段数 貌似数据比较弱,我的错误的线段树也过了
正解 改为区间的模型 右端点+1,表示端点
HDU 3016 开始读错题了……认为就是一个线段数覆盖的模型呢……然后看错了数据范围,开始按照PKU 2528的正解离散化……后来发现不用离散化果断过了,我的第一到线段树DP,就这么悲剧了
由于只能从左或者右端点下来,所以每个点可以到2个板。然后用线段树建dag图就是一个水的拓扑序DP了
poj 1436 把由于只有整数的区间,所以吧每个单位区间也当作一个点对于a, b的线段 插入 a * 2 到 b * 2 ,解决了相交于端点的问题,很巧妙的转换!!!向大牛学习了~
HDU 2795 一个水题,插入到叶子节点的简单模型,但是开始被我当作一个覆盖的统计模型了,SB了……
PKU 3667 (新学的内容)同时1A掉HOJ2687 还有 HDU 2871也是一个模型的题,让我抑郁的是那个get的实现我本来是打算在线段树中再加3个域,结果这么写太恶心了,然后看一下别人的程序,竟然用一个vector就可以过,感觉数据是不够猥琐呀,其中用了vector的insert和erase 我一直认为这两个函数是巨慢无比的,但是这题的效果还是非常好的 而且发现自己还有不好的习惯,一旦遇到做不动的题就不想写了……
以PKU 3667为例说一下这类题
一个经典的线段树,以前也见过这里类型,思考过但是一直没有做,这次找了一个这样的题A掉了
不过开始脑残了……贡献了几个WA
区间覆盖操作,找满足一定连续长度的最左边的区间
区间属性
int lcnt[N], rcnt[N], maxn[N],chk[N];
lcnt[N] 表示该区间总左边去最多能去多少,rcnt[N]同理是从右边。
chk[N]表示是否被覆盖过。-1表示不合法状态要查询子区间。
manx[N]表示该区间最大的连续长度是多少。
int all(int idx)//函数计算区间的最大长度
pass(int idx)//函数在insert或者query子区间是,继承区间的属性,更改区间的3个值
change(int idx,int d) 当区间的chk值发生改变时,计算重新计算3个值
显然当d==0时//表示该区间没有人住。3个值都为all(idx)
d== 1时为0
update(int idx)函数是最重要的操作,是每次insert回溯时改变父亲区间的属性。是这个线段树的核心
lcnt[idx] = lcnt[LEFT(idx)] + (lcnt[LEFT(idx)] == all(LEFT(idx)) ? lcnt[RIGHT(idx)] : 0);
rcnt[idx] = rcnt[RIGHT(idx)] + (rcnt[RIGHT(idx)] == all(RIGHT(idx)) ? rcnt[LEFT(idx)] : 0);
这个两句比较直观好理解。
maxn[idx] = max(max(maxn[LEFT(idx)], maxn[RIGHT(idx)]), rcnt[LEFT(idx)] + lcnt[RIGHT(idx)]);的计算是直观重要的
不用再算 maxn[idx] = (max(lcnt[idx], rcnt[idx]),maxn[idx])。原因如下
假如左儿子区间不是all的时候lcnt[idx] <= maxn[LEFT(idx)];右儿子区间同理。
而满的时候有lcnt[idx] == rcnt[LEFT(idx)] + lcnt[RIGHT(idx)];右儿子区间同理。
这个性质大大的化简的线段树的讨论过程。使得query时更加方便。
开始的时候由于maxn[idx]算错了,所以一直WA。
max(maxn[LEFT(idx)], maxn[RIGHT(idx)])这两个maxn被我写成了lcnt和rcnt
完全的代码如下,效率有点低,代码还是很直接美观的。

POJ 3667
#include < iostream >
#include
< cstring >
#include
< cstdio >
using namespace std;
class segment_tree
{
private :
const static int N = 50010 * 4 ;
int left[N],right[N];
int lcnt[N], rcnt[N], maxn[N],chk[N];
public :
#define LEFT(x) ((x) * 2)
#define RIGHT(x) ((x) * 2 + 1)
int all( int idx)
{
return right[idx] - left[idx] + 1 ;
}

void pass( int idx)
{
if (chk[idx] != - 1 )
{
chk[LEFT(idx)]
= chk[RIGHT(idx)] = chk[idx];
change(LEFT(idx), chk[idx]);
change(RIGHT(idx), chk[idx]);
chk[idx]
= - 1 ;
}
}

void change( int idx, int d)
{
lcnt[idx]
= rcnt[idx] = maxn[idx] = d == 0 ? all(idx) : 0 ;
}

void update( int idx)
{
lcnt[idx]
= lcnt[LEFT(idx)] + (lcnt[LEFT(idx)] == all(LEFT(idx)) ? lcnt[RIGHT(idx)] : 0 );
rcnt[idx]
= rcnt[RIGHT(idx)] + (rcnt[RIGHT(idx)] == all(RIGHT(idx)) ? rcnt[LEFT(idx)] : 0 );
maxn[idx]
= max(max(maxn[LEFT(idx)], maxn[RIGHT(idx)]), rcnt[LEFT(idx)] + lcnt[RIGHT(idx)]);
}

void build( int l, int r, int idx)
{
left[idx]
= l,right[idx] = r;
int mid = (l + r) / 2 ;
chk[idx]
= idx == 1 ? 0 : - 1 ;
lcnt[idx]
= rcnt[idx] = maxn[idx] = all(idx);
if (l == r) return ;
build(l, mid, LEFT(idx));
build(mid
+ 1 , r, RIGHT(idx));
}

void insert( int l, int r, int idx, int d)
{
if (left[idx] == l && right[idx] == r)
{
chk[idx]
= d;
change(idx, d);
return ;
}
pass(idx);
int mid = (left[idx] + right[idx]) / 2 ;
if (r <= mid)
insert(l, r, LEFT(idx), d);
else if (l > mid)
insert(l, r, RIGHT(idx), d);
else
{
insert(l, mid, LEFT(idx), d);
insert(mid
+ 1 , r, RIGHT(idx), d);
}
update(idx);
}

int query( int ans, int idx)
{
if (maxn[idx] < ans)
return 0 ;
if (right[idx] - left[idx] + 1 == ans)
return left[idx];
pass(idx);
if (maxn[LEFT(idx)] >= ans)
return query(ans, LEFT(idx));
else if (rcnt[LEFT(idx)] + lcnt[RIGHT(idx)] >= ans)
return right[LEFT(idx)] - rcnt[LEFT(idx)] + 1 ;
else if (maxn[RIGHT(idx)] >= ans)
return query(ans, RIGHT(idx));
}
}tree;
int main()
{
int n, m;
while (scanf( " %d %d " , & n, & m) == 2 )
{
tree.build(
1 , n, 1 );
int p, a, b;
while (m -- )
{
scanf(
" %d " , & p);
if (p == 1 )
{
scanf(
" %d " , & a);
int ans = tree.query(a, 1 );
printf(
" %d\n " , ans);
if (ans != 0 )
tree.insert(ans, ans
+ a - 1 , 1 , 1 );
}
else if (p == 2 )
{
scanf(
" %d %d " , & a, & b);
tree.insert(a, a
+ b - 1 , 1 , 0 );
}
}
}
return 0 ;
}

POJ 2892 最直观的是平衡树,用树状数组的findK的功能更酷一些,线段树找第K小也可以,据说有把线段树建和平衡树的一样的方法做?有待研究

POJ 2892
1 #include < iostream >
2 #include < cstring >
3 #include < cstdio >
4 #include < stack >
5 #include < cmath >
6   using namespace std;
7   const int MAXN = 50006 ;
8   const int INF = 100000 ;
9   int c[MAXN];
10
11   int lowbit( int k)
12 {
13 return k & ( - k);
14 }
15
16 void modify( int k, int val)
17 {
18 for ( int i = k; i < MAXN; i += lowbit(i))
19 c[i] += val;
20 }
21
22 int getsum( int k)
23 {
24 int ans = 0 ;
25 for ( int i = k; i > 0 ; i -= lowbit(i))
26 ans += c[i];
27 return ans;
28 }
29
30 int findK( int K) // 求第K小
31 {
32 int ans = 0 , cnt = 0 ;
33 for ( int i = log( double (MAXN - 1 )) / log( 2.0 ); i >= 0 ; i -- )
34 {
35 ans += ( 1 << i);
36 if (ans >= MAXN || cnt + c[ans] >= K)
37 ans -= ( 1 << i);
38 else
39 cnt += c[ans];
40 }
41 return ans + 1 ;
42 }
43
44
45 int main()
46 {
47 int n, m;
48 while (scanf( " %d %d " , & n, & m) == 2 )
49 {
50 stack < int > stk;
51 char cmd;
52 int a;
53 memset(c, 0 , sizeof (c));
54 modify( 1 , 1 );
55 modify(n + 2 , 1 );
56 while (m -- )
57 {
58 scanf( " %c " , & cmd);
59 if (cmd == ' D ' )
60 {
61 scanf( " %d " , & a);
62 a ++ ;
63 modify(a, 1 );
64 stk.push(a);
65 }
66 else if (cmd == ' R ' )
67 {
68 a = stk.top();
69 stk.pop();
70 modify(a, - 1 );
71 }
72 else
73 {
74 scanf( " %d " , & a);
75 a ++ ;
76 int k = getsum(a);
77 if (k - getsum(a - 1 ) == 1 )
78 printf( " %d\n " , 0 );
79 else
80 printf( " %d\n " , findK(k + 1 ) - findK(k) - 1 );
81 }
82 }
83 }
84 return 0 ;
85 }

此外HOJ 2910 一个线段树DP卡了……而且我无耻的利用了admin的身份看了一下数据,shit……就错了一组数据差1

3月19日更新:

线段树+扫描线的好题 HOJ 2723 POJ 也有原题stars in your window
以每个点位中心建立一个w-eps ,h - eps的矩形,然后求最大的面积带权并
可以证明在这样一个矩形中所有的点,是能包含该点覆盖点最多的矩形,是一个很重要的思想。
然后就是区间最值的问题了,开始的时候还写错了,丢人呀……

HOJ 1400
赤裸裸的线段树DP,但是由于我的英语一直没懂
简单的说一定要从1个开始连续的覆盖才可以。
还有就是插入的时候插入点就可以了,不许要插入区间。
我们可以考察两个线段,如果没有交集的话,那么前面的一条的任何一个子区间都不会被利用到。
如果有交集的话,他被利用的子区间必定包含右端点区间。

HOJ 1400
1 #include < iostream >
2 #include < cstring >
3 #include < cstdio >
4 #include < algorithm >
5 using namespace std;
6 class segment_tree
7 {
8 private :
9 static const int N = 50100 * 4 ;
10 static const int INF = 10000000 ;
11 int left[N], right[N], chk[N];
12 int minn[N];
13 #define LEFT(x) ((x) << 1)
14 #define RIGHT(x) ((x) << 1 | 1)
15 #define MID(x) ((left[x] + right[x]) >> 1)
16 public :
17 void build( int l, int r, int x)
18 {
19 left[x] = l, right[x] = r;
20 chk[x] = 0 ;
21 minn[x] = INF;
22 if (l == r) return ;
23 int mid = MID(x);
24 build(l, mid, LEFT(x));
25 build(mid + 1 , r, RIGHT(x));
26 }
27
28 void insert( int p, int x, int dp)
29 {
30 if (left[x] == p && right[x] == p)
31 {
32 minn[x] = min(minn[x], dp);
33 return ;
34 }
35 int mid = MID(x);
36 if (p <= mid)
37 insert(p, LEFT(x), dp);
38 else if (p > mid)
39 insert(p, RIGHT(x), dp);
40 minn[x] = min(minn[LEFT(x)], minn[RIGHT(x)]);
41 }
42
43 int query( int l, int r, int x)
44 {
45 if (left[x] == l && right[x] == r)
46 return minn[x];
47 int mid = MID(x);
48 if (r <= mid)
49 return query(l, r, LEFT(x));
50 else if (l > mid)
51 return query(l, r, RIGHT(x));
52 else
53 return min(query(l, mid, LEFT(x)), query(mid + 1 , r, RIGHT(x)));
54 }
55 }tree;
56 const int M = 500001 ;
57 pair < int , int > inter[M];
58 int main()
59 {
60 int n, m;
61 while (scanf( " %d %d " , & n, & m) == 2 )
62 {
63 tree.build( 1 , n, 1 );
64 for ( int i = 0 ;i < m;i ++ )
65 scanf( " %d %d " , & inter[i].first, & inter[i].second);
66 tree.insert( 1 , 1 , 0 );
67 for ( int i = 0 ;i < m;i ++ )
68 {
69 int dp = tree.query(inter[i].first, inter[i].second, 1 );
70 tree.insert(inter[i].second, 1 , dp + 1 );
71 }
72 printf( " %d\n " , tree.query(n, n, 1 ));
73 }
74 return 0 ;
75 }

顺便HOJ 2920

HOJ 2920
1 #include < iostream >
2 #include < cstring >
3 #include < cstdio >
4 #include < cmath >
5 #include < vector >
6 #include < functional >
7 #include < algorithm >
8 using namespace std;
9 #define lowbit(x) ((x) & (- (x)))
10 #define DEBUG(x) cout << #x << " " << x << endl;
11 const int N = 100010 ;
12 int c[N], a[N];
13
14 void modify( int * c, int x, int delta)
15 {
16 for ( int i = x;i < N;i += lowbit(i))
17 c[i] += delta;
18 }
19
20 int sum( int * c, int x)
21 {
22 int s = 0 ;
23 for ( int i = x;i > 0 ;i -= lowbit(i))
24 s += c[i];
25 return s;
26 }
27
28 int findK( int K) // 求第K小
29 {
30 int ans = 0 , cnt = 0 ;
31 for ( int i = log(N - 1 ) / log( 2 ); i >= 0 ; i -- )
32 {
33 ans += ( 1 << i);
34 if (ans >= N || cnt + c[ans] >= K)
35 ans -= ( 1 << i);
36 else
37 cnt += c[ans];
38 }
39 return ans + 1 ;
40 }
41
42 int main()
43 {
44 int n;
45 while (scanf( " %d " , & n) == 1 && n)
46 {
47 for ( int i = 1 ;i <= n;i ++ )
48 scanf( " %d " , & a[i]);
49 memset(c, 0 , sizeof (c));
50 for ( int i = 1 ;i <= n;i ++ )
51 modify(c, i, 1 );
52 int cnt = n, k;
53 if (a[ 1 ] % n == 0 )
54 k = n;
55 else
56 k = a[ 1 ] % n;
57 printf( " %d " , k);
58 modify(c, k, - 1 );
59 k = sum(c, k);
60 cnt -- ;
61 for ( int i = 2 ;i <= n;i ++ )
62 {
63 k += a[i];
64 if ((k) % cnt)
65 k = findK(k % cnt);
66 else
67 k = findK(cnt);
68 printf( " %d " , k);
69 modify(c, k, - 1 );
70 k = sum(c, k);
71 cnt -- ;
72 }
73 puts( "" );
74 }
75 return 0 ;
76 }

树状数组,线段树同样可以解,不过这个树状数组求第K大真的很酷,所以又一次上树状数组了

hdu1255 覆盖的面积
我的极其糟糕的写法基本上退化成数了……正解是扫描线+记录被覆盖两次的线段长。查询是O(1)的,而我的方法可能退化成O(nlogn)
代码就不现丑了

POJ 2828 比较直观的算法是二分+树状数组,但是这题确实线段树代码更帅一下!!
基于这样的考虑,最后一个人到的位置一定是队列的确定位置,那么我们把这个人的位置拿掉以后,剩下的队列就是前n - 1个人的位置,那么倒数第二个人同理,取得其在当前队列中的位置。所以只要从后到前确认每个人的位置就可以了!所以此题只要一个query就可以了

query代码如下:

  int query(int p, int x)
        {
            cnt[x]--;
            if(left[x] == right[x])
                return left[x];
            if(cnt[LEFT(x)] >= p)
                return query(p, LEFT(x));
                    else
                return query(p - cnt[LEFT(x)], RIGHT(x));
        }

pku2886 Who Gets the Most Candies?(AC)

残留题目

pku3225 Help with Intervals

pku2464 Brownie Points II

pku3145 Harmony Forever

pku2991 Crane

hdu1823 Luck and Love

六道做不下去了,以后再说吧。

自己的效率实在是太低了……线段树,放一下吧,换AC自动机

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