线段树专题

1、对点操作->查询区间

Description

Lily 特别喜欢养花,但是由于她的花特别多,所以照料这些花就变得不太容易。她把她的花依次排成一行,每盆花都有一个美观值。如果Lily把某盆花照料的好的话,这盆花的美观值就会上升,如果照料的不好的话,这盆花的美观值就会下降。有时,Lily想知道某段连续的花的美观值之和是多少,但是,Lily的算术不是很好,你能快速地告诉她结果吗?

Input 

	第一行一个整数T,表示有T组测试数据。
每组测试数据的第一行为一个正整数N(N<=50000),表示Lily有N盆花。接下来有N个正整数,第i个正整数ai表示第i盆花的初始美观值(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1)Add i j, i和j为正整数,表示第i盆花被照料的好,美观值增加j(j<=30)
(2)Sub i j, i和j为正整数,表示第i盆花被照料的不好,美观值减少j(j<=30)
(3)Query i j, i和j为正整数,i<=j,表示询问第i盆花到第j盆花的美观值之和
(4)End,表示结束,这条命令在每组数据最后出现
每组数据的命令不超过40000条
	对于第i组数据,首先输出"Case i:"和回车。
对于每个"Query i j"命令,输出第i盆花到第j盆花的美观值之和。

Sample Input

1

9
7 9 8 4 4 5 4 2 7
Query 7 9
Add 4 9
Query 3 6
Sub 9 6
Sub 3 3
Query 1 9
End

 

Sample Output

Case 1:

133050

 

 

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define UP(i,a,b) for(int i=a;i<=b;i++)
#define DN(i,a,b) for(int i=a;i>=b;i--)

struct node
{
  int l, m, r, sum;
} nd[4 * 50005];

void build(int i, int l, int r)
{
  nd[i].l = l;
  nd[i].r = r;
  nd[i].m = (l + r) / 2;

  if(l == r)
  {
    scanf("%d", &nd[i].sum);
    return;
  }

  build(i * 2, l, nd[i].m);
  build(i * 2 + 1, nd[i].m + 1, r);

  nd[i].sum = nd[i * 2].sum + nd[i * 2 + 1].sum;//At this point, two child trees have already been built up whose top nodes already have sum value
}

void update(int i, int n, int x)
{
  nd[i].sum += x;

  if(nd[i].l == nd[i].r) return;

  if(n <= nd[i].m)
    update(i * 2, n, x);
  else
    update(i * 2 + 1, n, x);
}

int query(int i, int a, int b)
{
  if(nd[i].l == a && nd[i].r == b)
    return nd[i].sum;

  if(b <= nd[i].m)
    return query(i * 2, a, b);

  if(nd[i].m < a)
    return query(i * 2 + 1, a, b);

  return query(i * 2, a, nd[i].m) + query(i * 2 + 1, nd[i].m + 1, b);

}

int main()
{
  char s[20];
  int t, n, x, a, b;

  scanf("%d", &t);

  UP(i, 1, t)
  {
    printf("Case %d:\n", i);
    scanf("%d", &n);

    build(1, 1, n);

    while(1)
    {
      scanf("%s", s);

      if(s[0] == 'A')
      {
        scanf("%d%d", &n, &x);
        update(1, n, x);
      }
      else if(s[0] == 'S')
      {
        scanf("%d%d", &n, &x);
        update(1, n, -x);
      }
      else if(s[0] == 'Q')
      {
        scanf("%d%d", &a, &b);
        printf("%d\n", query(1, a, b));
      }
      else break;
    }
  }
}

 

 

2、对区间操作->查询区间

Description

给出了一个序列,你需要处理如下两种询问。

"C a b c"表示给[a, b]区间中的值全部增加c (-10000 ≤ c ≤ 10000)。

"Q a b" 询问[a, b]区间中所有值的和。

Input

第一行包含两个整数N, Q。1 ≤ N,Q ≤ 100000.

第二行包含n个整数,表示初始的序列A (-1000000000 ≤ Ai ≤ 1000000000)。

接下来Q行询问,格式如题目描述。

Output

对于每一个Q开头的询问,你需要输出相应的答案,每个答案一行。

Sample Input

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

Sample Output

4
55
9
15

LTE代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define UP(i,a,b) for(int i=a;i<=b;i++)
#define DN(i,a,b) for(int i=a;i>=b;i--)

struct node
{
  LL l, m, r, sum;
} n[4 * 100005];

void build(int i, int l, int r)
{
  n[i].l = l;
  n[i].r = r;
  n[i].m = (l + r) / 2;

  if(l == r)
  {
    scanf("%lld", &n[i].sum);
    return;
  }

  build(i * 2, l, n[i].m);
  build(i * 2 + 1, n[i].m + 1, r);

  n[i].sum = n[i * 2].sum + n[i * 2 + 1].sum;
}



///

void add(int i, int a, int b, int x)
{
  n[i].sum += (b - a + 1) * x; //while we are searching for the target interval by going downwards, we need to add the total delta value to current parent level

  //if(a == b) return; This exit is wrong! What if our target interval is [2,2] and we are at the firs level? We still need to go down!

  if(n[i].l == n[i].r) return;//That's the right exit situation where the node is a single element

  if(b <= n[i].m) //three situations referring to Figure 1
    add(i * 2, a, b, x);

  else if(n[i].m < a)
    add(i * 2 + 1, a, b, x);

  else
  {
    add(i * 2, a, n[i].m, x);
    add(i * 2 + 1, n[i].m + 1, b, x);
  }
}

/*LTE because we update all nodes in the interval every single time when we do
the add operation. In fact, we can just log the add operations without updating
all nodes and update them all at once when a query happens*/

/


LL query(int i, int a, int b)
{
  if(n[i].l == a && n[i].r == b)
    return n[i].sum;

  if(b <= n[i].m)
    return query(i * 2, a, b);

  if(n[i].m < a)
    return query(i * 2 + 1, a, b);

  return query(i * 2, a, n[i].m) + query(i * 2 + 1, n[i].m + 1, b);
}

int main()
{
  int n, q, a, b, x;
  char s[2];

  scanf("%d%d", &n, &q);
  build(1, 1, n);

  while(q--)
  {
    scanf("%s", s);

    if(s[0] == 'C')
    {
      scanf("%d%d%d", &a, &b, &x);
      add(1, min(a, b), max(a, b), x);
    }
    else
    {
      scanf("%d%d", &a, &b);

      printf("%lld\n", query(1, a, b));
    }
  }
  return 0;
}//JZ

 

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define UP(i,a,b) for(int i=a;i<=b;i++)
#define DN(i,a,b) for(int i=a;i>=b;i--)

struct node
{
  LL l, m, r, sum, childrenCache;

} n[4 * 100005];

void build(int i, int l, int r)
{
  n[i].l = l;
  n[i].r = r;
  n[i].m = (l + r) / 2;
  n[i].childrenCache = 0;

  if(l == r)
  {
    scanf("%lld", &n[i].sum);
    return;
  }

  build(i * 2, l, n[i].m);
  build(i * 2 + 1, n[i].m + 1, r);

  n[i].sum = n[i * 2].sum + n[i * 2 + 1].sum;
}


void add(int i, int a, int b, int x) //only update the first matched interval, levels below are not considered
{
  n[i].sum += (b - a + 1) * x; //while we are searching for the target interval by going downwards, we need to add the total delta value to current parent level

  if(n[i].l == a && n[i].r == b)
  {
    n[i].childrenCache += x; //childrenCache is only for children because the sum of current level has been updated by code line 1 && it is "+=", not "="
    return;//all levels below i are not updated
  }

  if(b <= n[i].m) //three situations referring to Figure 1
    add(i * 2, a, b, x);

  else if(n[i].m < a)
    add(i * 2 + 1, a, b, x);

  else
  {
    add(i * 2, a, n[i].m, x);
    add(i * 2 + 1, n[i].m + 1, b, x);
  }
}

void pushDown(int i)
{
  if(n[i].childrenCache == 0) return;

  //only push down by one level, step by step
  n[i * 2].sum += (n[i * 2].r - n[i * 2].l + 1) * n[i].childrenCache;
  n[i * 2 + 1].sum += (n[i * 2 + 1].r - n[i * 2 + 1].l + 1) * n[i].childrenCache;

  n[i * 2].childrenCache += n[i].childrenCache; //remember that childrenCache is for children
  n[i * 2 + 1].childrenCache += n[i].childrenCache;

  n[i].childrenCache = 0; //Don't forget this step!!!!!!
}

LL query(int i, int a, int b)
{
  if(n[i].l == a && n[i].r == b)
    return n[i].sum;

  pushDown(i);
  /*this step being executed indicates that we didn't return at code line 1.
  Thus, we are at a parent level, which means that we need to push down the
  logged childrenCache of this parent level toward our target step by step*/

  if(b <= n[i].m)
    return query(i * 2, a, b);

  if(n[i].m < a)
    return query(i * 2 + 1, a, b);

  return query(i * 2, a, n[i].m) + query(i * 2 + 1, n[i].m + 1, b);
}

int main()
{
  int n, q, a, b, x;
  char s[2];

  scanf("%d%d", &n, &q);
  build(1, 1, n);

  while(q--)
  {
    scanf("%s", s);

    if(s[0] == 'C')
    {
      scanf("%d%d%d", &a, &b, &x);
      add(1, min(a, b), max(a, b), x); //be careful about the order of a & b
    }
    else
    {
      scanf("%d%d", &a, &b);
      printf("%lld\n", query(1, min(a,b), max(a,b))); //be careful about the order of a & b
    }
  }
  return 0;
}//JZ

 

3、对区间操作->查询点

Description

N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?

Input

每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。 
当N = 0,输入结束。

Output

每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。

Sample Input

3

1 1

2 2

3 3

3

1 1

1 2

1 3

0

Sample Output

1 1 1

3 2 1

 

 

注意!这里如果每一次操作的时候都把区间下推更新的话会超时,所以记住了一定不能无脑套模板,任何题到手都应该先算复杂度。正确方法是先用 t[i].n++ 存起来,最后再从头往下处理。

代码:

//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define UP(i,a,b) for(int i=a;i<=b;i++)
#define DN(i,a,b) for(int i=a;i>=b;i--)

struct node
{
  int l, r, m, cache;
} n[4 * 100005];

void build(int i, int l, int r)
{
  n[i].l = l;
  n[i].r = r;
  n[i].m = (l + r) / 2;
  n[i].cache = 0;

  if(l == r)
    return;

  build(i * 2, l, n[i].m);
  build(i * 2 + 1, n[i].m + 1, r);
}

void add(int i, int a, int b)
{
  if(n[i].l == a && n[i].r == b)
  {
    n[i].cache++;
    return;
  }

  if(b <= n[i].m)
    add(i * 2, a, b);
  else if(n[i].m < a)
    add(i * 2 + 1, a, b);
  else
  {
    add(i * 2, a, n[i].m);
    add(i * 2 + 1, n[i].m + 1, b);
  }
}

void query(int i)
{
  if(n[i].l == n[i].r)
  {
    if(n[i].l != 1)
      printf(" ");

    printf("%d", n[i].cache);

    return;
  }

  if(n[i].cache != 0)
  {
    n[i * 2].cache += n[i].cache;
    n[i * 2 + 1].cache += n[i].cache;

    n[i].cache = 0;
  }

  query(i * 2);
  query(i * 2 + 1);
}

int main()
{
  int n,a,b;

  while(~scanf("%d", &n) && n != 0)
  {
    build(1, 1, n);

    while(n--)
    {
      scanf("%d%d", &a, &b);
      add(1, min(a, b), max(a, b));
    }

    query(1);
    cout << endl;
  }
  return 0;
}//JZ

 

简单做法O(n) 

//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define UP(i,a,b) for(int i=a;i<=b;i++)
#define DN(i,a,b) for(int i=a;i>=b;i--)

int n[100005];

int main()
{
  int N, a, b;

  while(~scanf("%d", &N) && N != 0)
  {
    memset(n,0,sizeof(n));

    UP(i,1,N)
    {
      scanf("%d%d", &a, &b);
      n[a]++;
      n[b+1]--;
    }

    cout<<n[1];
    int tem=n[1];

    UP(i,2,N)
    {
      tem = tem+n[i];
      cout<<" "<<tem;
    }

    cout<<endl;
  }
  return 0;
}//JZ

 

 

 

 

 

 

 

 

 

4、最小逆序数

Description

The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj. 

For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following: 

a1, a2, ..., an-1, an (where m = 0 - the initial seqence) 
a2, a3, ..., an, a1 (where m = 1) 
a3, a4, ..., an, a1, a2 (where m = 2) 
... 
an, a1, a2, ..., an-1 (where m = n-1) 

You are asked to write a program to find the minimum inversion number out of the above sequences. 

Input

The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1. 

Output

For each case, output the minimum inversion number on a single line. 

Sample Input

10 1 3 6 9 0 8 5 7 4 2

Sample Output

16

 

 

这道题有多种做法:http://www.verydemo.com/demo_c180_i41274.html

 

代码1:暴力(也不是无脑暴力,有个递推公式)

 

#include<stdio.h>//code source:hnust_xiehonghao
int a[5005];
int main()
{
  int n,i,j,ans=999999999;
  while(~scanf("%d",&n))
    {
      ans=999999999;
      for(i=0; i<n; i++)
        scanf("%d",&a[i]);

      int cnt=0;

      for(i=0; i<n; i++)
        for(j=i+1; j<n; j++)
          {
            if(a[i]>a[j])
              cnt++;
          }
      if(ans>cnt)  ans=cnt;

      for(i=0; i<n; i++)
        {
          cnt=cnt-a[i]+n-1-a[i];
          /*因为数据是从0 ~ n-1的连续自然数,所以当队首元素移到队尾后,
          会减少a[i]个逆序对,然后增加(n-1)-a[i]个逆序对(注意最大数是n-1,不是n)
          比如1移到队尾,那么1>0这个逆序对就会减少,2>1,3>1,4>1这些逆序对就会增加。*/

          if(ans>cnt)
            ans=cnt;
        }
      printf("%d\n",ans);
    }
  return 0;
}

 

代码2:由于这道题是线段树专题里面的,我还以为会把每一种情况都构造一次线段树来做,结果就只是把求原序列逆序数那里改成线段树,后来仍然用递推公式来做,真是醉了。求原序列逆序数的线段树就是之前提到过的对点进行操作类型。输入一个a[i]就query(1 ,a [i]+1 , n-1)(思路是这样,但操作要变通一下),找到已存在的序列中比它大的元素的个数。

 

 

 

#include<stdio.h>
int a[5005];
struct haha
{
  int left;
  int right;
  int num;
} node[5005*4];

void build(int left,int right,int nd)
{
  node[nd].left=left;
  node[nd].right=right;
  node[nd].num=0;

  if(left==right)
    return ;
  int mid=(left+right)/2;

  build(left,mid,nd*2);
  build(mid+1,right,nd*2+1);
}

int query(int left,int right,int nd)
{
  int mid=(node[nd].left+node[nd].right)/2;

  if(node[nd].left==left&&node[nd].right==right)
    return node[nd].num;

  if(right<=mid)
    return query(left,right,nd*2);

  else if(left>mid)
    return query(left,right,nd*2+1);

  else
    return query(left,mid,nd*2)+query(mid+1,right,nd*2+1);
}
void update(int pos,int nd)
{
  if(node[nd].left==node[nd].right)
    {
      node[nd].num++;
      return ;
    }

  int mid=(node[nd].left+node[nd].right)/2;

  if(pos<=mid)
    update(pos,nd*2);
  else
    update(pos,nd*2+1);

  node[nd].num=node[nd*2].num+node[nd*2+1].num;
}
int main()
{
  int n,i,j;
  while(scanf("%d",&n)!=EOF)
    {
      for(i=0; i<n; i++)
        scanf("%d",&a[i]);

      build(0,n-1,1);

      int sum=0;
//      for(i=0; i<n; i++)//错误方法
//        {
//          update(a[i],1);
//          sum+=query(a[i]+1,n-1,1);
//        }

/*sb一样access violation好几次,这里的query(a[i],n-1,1)千万不能写成query(a[i]+1,n-1,1)(虽然按道理说应该是这样),
因为query函数必须要保证左边的值小于等于右边的,倘若这里a[i]就是n-1,那么就变成了query(n,n-1,1),
这明显是不合法的。所以就写成query(a[i],n-1,1)
(还未更新时a[i]的数量为0,所以这里query(a[i],n-1,1)和query(a[i]+1,n-1,1)在结果上没区别),
然后把a[i]这个点的插入更新放在query后面*/
      for(i=0; i<n; i++)
        {
          sum+=query(a[i],n-1,1);
          update(a[i],1);
        }

      int ans=99999999;
      if(ans>sum)  ans=sum;
      for(i=0; i<n; i++)
        {
          sum=sum-a[i]+n-1-a[i];
          if(ans>sum) ans=sum;
        }

      printf("%d\n",ans);
    }
  return 0;
}

 

 

5、插队问题(模板题)

 

 

 

 

Description

Railway tickets were difficult to buy around the Lunar New Year in China, so we must get up early and join a long queue…

The Lunar New Year was approaching, but unluckily the Little Cat still had schedules going here and there. Now, he had to travel by train to Mianyang, Sichuan Province for the winter camp selection of the national team of Olympiad in Informatics.

It was one o’clock a.m. and dark outside. Chill wind from the northwest did not scare off the people in the queue. The cold night gave the Little Cat a shiver. Why not find a problem to think about? That was none the less better than freezing to death!

People kept jumping the queue. Since it was too dark around, such moves would not be discovered even by the people adjacent to the queue-jumpers. “If every person in the queue is assigned an integral value and all the information about those who have jumped the queue and where they stand after queue-jumping is given, can I find out the final order of people in the queue?” Thought the Little Cat.

Input

There will be several test cases in the input. Each test case consists of N + 1 lines where N (1 ≤ N ≤ 200,000) is given in the first line of the test case. The next N lines contain the pairs of values Posi and Vali in the increasing order of i (1 ≤ i ≤ N). For each i, the ranges and meanings of Posiand Vali are as follows:

  • Posi ∈ [0, i − 1] — The i-th person came to the queue and stood right behind the Posi-th person in the queue. The booking office was considered the 0th person and the person at the front of the queue was considered the first person in the queue.
  • Vali ∈ [0, 32767] — The i-th person was assigned the value Vali.

There no blank lines between test cases. Proceed to the end of input.

Output

For each test cases, output a single line of space-separated integers which are the values of people in the order they stand in the queue.

Sample Input

4
0 77
1 51
1 33
2 69
4
0 20523
1 19243
1 3890
0 31492

Sample Output

77 33 69 51
31492 20523 3890 19243

 

 

 

题意:有n个数,进行n次操作,每次操作有两个数pos, ans。pos的意思是把ans放到第pos 位置的后面,pos后面的数就往后推一位。最后输出每个位置的ans。

 

思路(来自青山绿水之辈):根据题 目可知,最后插入的位置的数才是最终不变的数,所以可以从最后的输入作第1个放入,依此类推,倒插入。在插入时,要注意,一个数放入之后那么这个位置就不用管了,那么树中所有的空位置就是余下的数所对应的位置,也就是把余下的数又可以看作是一个新的集合。那么每次插入都是当前集合的第1次放。

 

代码:

 

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<iostream>
#include<stack>
#include<vector>
#include<queue>
#include<string>
#include<algorithm>
using namespace std;

#define MaxSize 200005

int pos[MaxSize];
int val[MaxSize];

int A[MaxSize];

struct node
{
  int l,r;
  int space;
};

node tree[MaxSize*4];

void build(int a,int b,int k)
{
  tree[k].l = a;
  tree[k].r = b;

  if(a==b)
    {
      tree[k].space = 1;
      return ;
    }

  int mid = (a+b)/2;

  build(a,mid,k*2);
  build(mid+1,b,k*2+1);

  tree[k].space = tree[k*2].space + tree[k*2+1].space;
}

void insert(int p,int val,int k)
{
  if(tree[k].l == tree[k].r)
    {
      tree[k].space--;
      A[tree[k].l] = val;
      return ;
    }

  if(p <= tree[k*2].space)
    insert(p,val,k*2);
  else
    insert(p-tree[k*2].space,val,k*2+1);

  tree[k].space = tree[k*2].space + tree[k*2+1].space;
}

int main()
{
  int N;
  while(~scanf("%d",&N))
    {
      for(int i=1; i<= N; i++)
        scanf("%d%d",&pos[i],&val[i]);

      build(0,N-1,1);

      for(int i=N; i>=1; i--)
          insert(pos[i]+1,val[i],1);

      for(int i=0; i<N; i++)
        printf("%d ",A[i]);

      printf("\n");
    }
  return 0;
}

 

6、连续区间

 

 

I:苦逼的种树人

总时间限制: 

1000ms 

内存限制: 

65535kB

描述

有一个苦逼的种树人XX,需要种一排树,为什么说苦逼呢?因为总有一些熊孩子去把树苗拔掉(具体拔掉做什么,你们可以YY一下)。为了简化问题,把可以种树的位置编号为1-n。XX每次选择一个位置种树(如果已经有树了,则忽略),而熊孩子每次也选择一个位置拔树(如果没有树,则忽略)。现在XX想知道每次种树或者拔树后的连续有树的区间的最长长度。

输入

多组数据(大约10组)。对于每组数据:
第一行n,m;
接下来m行,每行q,a;
其中,q为1时表示XX种树,q为2时表示熊孩子拔树,a表示位置。
数据范围:1<=n,m<=10000,1<=q<=2,1<=a<=n。

输出

对于每组数据,输出m行,每次操作后的结果。

样例输入

4 5
1 1
1 3
1 4
2 4
1 2

样例输出

1
1
2
1
3

提示

开始状态都没有种树

=========================================================================================

思路:

我们要从线段树的所有区间中往上推,找出所有区间中最大的连续长度。看似tree[i].max_lenth=max( tree[i*2].max_length, tree[i*2+1].max_legnth)就行了。假如第1棵树到第7棵树1111011,第8棵树到第14棵树1110001。如果用这样的思路,父节点的最大连续就应该是max(4 , 3),为4。但是我们可以明显看出中间连接部分可以形成新的连续,而这里这个新连续恰好比左右儿子的max_length都大。所以这个思路明显是不对的。我们也从这个例子中知道,我们应该保留每个区间的左连续长度和右连续长度,在合并的时候,在连接部分进行相加,再来和左右儿子原来的max_length进行比较。

代码:

 

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<vector>
#include<queue>
#include<stack>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;

#define MaxSize 10005
#define inf 0x3f3f3f3f
#define LL long long int

struct node
{
  int l,r,mid,l_max,r_max,t_max;//l_max:从最左边开始的最长连续个数 r_max:从最右边开始的最长连续个数 t_max:整个区间中的最长连续个数
} tree[MaxSize*4];

void build(int i, int l, int r)
{
  tree[i].l=l;
  tree[i].r=r;
  tree[i].mid=(l+r)/2;
  tree[i].l_max=tree[i].r_max=tree[i].t_max=0;//一开始没有树,长度都是0

  if(l==r) return;

  build(i*2,tree[i].l,tree[i].mid);
  build(i*2+1,tree[i].mid+1,tree[i].r);
}

void operate(int i, int num, int x)
{
  if(tree[i].l==tree[i].r)
    {
      tree[i].l_max=tree[i].r_max=tree[i].t_max=num;//对于子节点,这三者是一样的
      return;
    }

  if(x<=tree[i].mid)
    operate(i*2,num,x);

  else
    operate(i*2+1,num,x);

操作完拔树种树之后,俩儿子合并操作///

  tree[i].t_max = max( max(tree[i*2].t_max,tree[i*2+1].t_max) , tree[i*2].r_max+tree[i*2+1].l_max);
  /*两个儿子合并的时候,如果左儿子有右连续,右儿子有左连续,中间一合并,新连续的长度就可能超过左右儿子的t_max,从而成为新的t_max*/


  /*举个例子:左儿子111000011,右儿子10001111,我们在合并的时候,他们的父节点的左连续长度就应该是左儿子的左连续长度,
  右连续长度就应该是右儿子的右连续长度。但是有另外一种情况,左儿子11111111,右儿子11100011.这样左儿子是满的情况下,
  父节点的左连续长度就应该是左儿子连续长度加上它与右儿子连接的那部分的长度*/

  if(tree[i*2].l_max == tree[i*2].r-tree[i*2].l+1)//如果左儿子满,父节点的左连续就应该是左连续长度加它与右儿子连接的那部分的长度
    tree[i].l_max=tree[i*2].l_max+tree[i*2+1].l_max;//如果不是满的(即从左尽头到与右儿子连接部分之间有间隔),父节点的左连续长度就应该左儿子的左连续长度
  else
    tree[i].l_max=tree[i*2].l_max;

  if(tree[i*2+1].r_max == tree[i*2+1].r-tree[i*2+1].l+1)
    tree[i].r_max=tree[i*2+1].r_max+tree[i*2].r_max;
  else
    tree[i].r_max=tree[i*2+1].r_max;
}

int main()
{
  int n,m,q,a;
  while(~scanf("%d%d",&n,&m))
    {
      build(1,1,n);
      while(m--)
        {
          scanf("%d%d",&q,&a);

          operate(1,q%2,a);//q为1时,q%2==1,让那个位置变为1,表示有树。q为2时,q%2==0,让那个位置变为0,表示没有有树。

          printf("%d\n",tree[1].t_max);
        }
    }
  return 0;
}//FROM CJZ

 

 

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值