[关闭]
@wwwqeqeqeqe 2019-06-17T08:39:57.000000Z 字数 6379 阅读 612

线段树题目讲解(2019.6.18)

线段树


A Ultra-QuickSort (POJ 2299)

题目大意:

给你n个乱序的数字,求出如果使用冒泡排序所需要交换的次数。(n≤500000)

解题思路:

最初,给每个节点赋值为1建立一棵线段树,对原数组进行排序,得到排序完成后每个数字在原数组的下标。再对这些下标进行操作,查找所有[1,index[i])之间有多少个1,这些1的个数就是我当前数字进行冒泡排序移动到对应位置所需要的移动的次数,ans加上这个数需要移动的步数后,删除这个数,并对线段树进行操作。最后,将所有的数全部跑完一遍之后,得到答案。

AC代码:

线段树:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cmath>
  6. using namespace std;
  7. const int maxn=5e5+5;
  8. int Tree[maxn<<2], index[maxn], num[maxn];
  9. int n;
  10. int cmp(const int i, const int j)
  11. {
  12. return num[i] < num[j];
  13. }
  14. void Edit(int k, int l, int r, int num, int value)
  15. {
  16. Tree[k] += value;
  17. if(r==l) return ;
  18. int mid = (l+r) >> 1;
  19. if(num <= mid) Edit(k<<1, l, mid, num, value);
  20. else Edit(k<<1|1, mid+1, r, num, value);
  21. }
  22. int Search(int k, int l, int r, int L, int R)
  23. {
  24. if(L<=l && r<=R) return Tree[k];
  25. int mid = (l+r) >> 1;
  26. int ans = 0;
  27. if(L <= mid) ans += Search(k<<1, l, mid, L, R);
  28. if(R > mid) ans += Search(k<<1|1, mid+1, r, L, R);
  29. return ans;
  30. }
  31. int main()
  32. {
  33. while(~scanf("%d", &n) && n)
  34. {
  35. memset(Tree,0,sizeof(Tree));
  36. memset(num,0,sizeof(num));
  37. memset(index,0,sizeof(index));
  38. for(int i=1; i<=n; i++)
  39. {
  40. scanf("%d", &num[i]);
  41. Edit(1, 1, n, i, 1);
  42. index[i] = i;
  43. }
  44. sort(index+1, index+n+1,cmp);
  45. long long ans = 0;
  46. for(int i=1; i<=n; i++)
  47. {
  48. ans += (Search(1, 1, n, 1, index[i])-1);
  49. Edit(1, 1, n, index[i], -1);
  50. }
  51. printf("%lld\n", ans);
  52. }
  53. return 0;
  54. }

树状数组:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cmath>
  6. using namespace std;
  7. const int maxn=5e5+5;
  8. int a[maxn],vis[maxn],n;
  9. struct node
  10. {
  11. int mark,val;
  12. };
  13. node aa[maxn];
  14. bool cmp(node x,node y)
  15. {
  16. return x.val<y.val;
  17. }
  18. int lowbit(int x)
  19. {
  20. return x&(-x);
  21. }
  22. void update(int x,int val)
  23. {
  24. while(x<=n)
  25. {
  26. vis[x]+=val;
  27. x+=lowbit(x);
  28. }
  29. }
  30. long long sum(int x)
  31. {
  32. long long an=0;
  33. while(x>=1)
  34. {
  35. an+=vis[x];
  36. x-=lowbit(x);
  37. }
  38. return an;
  39. }
  40. int main()
  41. {
  42. while(cin >> n && n)
  43. {
  44. for(int i=1; i<=n; i++)
  45. {
  46. cin >> aa[i].val;
  47. aa[i].mark=i;
  48. }
  49. sort(aa+1,aa+n+1,cmp);
  50. for(int i=1;i<=n;i++)
  51. {
  52. a[aa[i].mark]=i;
  53. }
  54. long long ans=0;
  55. memset(vis,0,sizeof(vis));
  56. for(int i=1;i<=n;i++)
  57. {
  58. update(a[i],1);
  59. ans+=i-sum(a[i]);
  60. }
  61. cout << ans << endl;
  62. }
  63. return 0;
  64. }

B Stars (POJ 2352)
题目大意:

给出n个点,表示有n颗星星,以这个点为起点向x,y轴作垂直线,与坐标轴围成一个矩形,矩形内的所有星星的数量(不包含自己)就是这个星星的level。题目按照y轴坐标升序,如果y轴坐标相同,按照x轴坐标升序给出所有的星星,输出所有level从0~n-1各个等级有多少颗星星。

解题思路:

因为输入是按照先y轴再x轴递增的顺序给出的,因此,对于第i颗星星,它的level就是之前出现过的星星中,x小于等于它的所有星星的总数(因为前面出现过的y一定小于后面出现的星星的y值)。
因此,我们需要建立一棵树,用于记录所有星星的x值,方便求出所有x值小于等于当前星星x值。

AC代码:

线段树:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cmath>
  6. using namespace std;
  7. const int maxn=32000+5;
  8. int sum[maxn<<2],level[maxn<<2];
  9. void update(int k,int l,int r,int data)
  10. {
  11. sum[k]++;
  12. if(l==r) return;
  13. int mid=(l+r)>>1;
  14. if(data <= mid) update(k<<1, l, mid, data);
  15. else update(k<<1|1, mid+1, r, data);
  16. }
  17. int query(int k,int l,int r,int ll,int rr)
  18. {
  19. if(l==ll && r==rr)
  20. {
  21. return sum[k];
  22. }
  23. int mid = (l+r)>>1;
  24. if(rr <= mid) return query(k<<1, l, mid,ll,rr);
  25. else if(ll > mid) return query(k<<1|1, mid+1, r,ll,rr);
  26. else return query(k<<1, l, mid,ll,mid)+query(k<<1|1, mid+1, r,mid+1,rr);
  27. }
  28. int main()
  29. {
  30. int n,x,y;
  31. while(~scanf("%d",&n))
  32. {
  33. memset(sum, 0, sizeof(sum));
  34. memset(level, 0, sizeof(level));
  35. for(int i=0; i<n; ++i)
  36. {
  37. scanf("%d%d",&x,&y);
  38. x++;
  39. level[query(1,1,maxn,1,x)]++;
  40. update(1,1,maxn,x);
  41. }
  42. for(int i=0; i<n; ++i)
  43. printf("%d\n",level[i]);
  44. }
  45. return 0;
  46. }

树状数组:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cmath>
  6. using namespace std;
  7. const int maxn=32005;
  8. int c[maxn];
  9. int mark[maxn];
  10. int n;
  11. int x,y;
  12. int lowbit(int x)
  13. {
  14. return x&(-x);
  15. }
  16. void update(int x,int val)
  17. {
  18. while(x<maxn)
  19. {
  20. c[x]+=val;
  21. x+=lowbit(x);
  22. }
  23. }
  24. int sum(int x)
  25. {
  26. int ans=0;
  27. while(x>=1)
  28. {
  29. ans+=c[x];
  30. x-=lowbit(x);
  31. }
  32. return ans;
  33. }
  34. int main()
  35. {
  36. cin >> n;
  37. memset(mark,0,sizeof(mark));
  38. memset(c,0,sizeof(c));
  39. for(int i=0;i<n;i++)
  40. {
  41. cin >> x >> y;
  42. int t=sum(x+1);
  43. mark[t]++;
  44. update(x+1,1);
  45. }
  46. for(int i=0;i<n;i++)
  47. cout << mark[i] << endl;
  48. return 0;
  49. }

C Mayor's posters (POJ 2528)
题目大意:

题目给出一堵墙,有n个人(1≤n≤10000),每个人依次贴一张海报,每张海报的范围为li到ri(1≤li≤ri≤10000000),求最后能看见多少张海报。

解题思路:

由于题目数据范围很大,但是人的个数很少,因此,我们需要对数据进行离散化,将题目给出的范围按照相互位置进行离散化,将数据范围缩小。数据范围缩小后,将变化后的数据插入线段树中,由于我们需要的是相关的覆盖信息,且是后面的覆盖前面的,因此,只需要将数组从尾到头进行计算即可,每次计算判断是否存在没有被覆盖的范围,如果有,ans++。

AC代码:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cmath>
  6. using namespace std;
  7. const int maxn = 20000 + 5;
  8. int pl[maxn], pr[maxn], n;
  9. int s[maxn*4], e[maxn*4];
  10. bool covered[maxn*4];
  11. int port[maxn], cnt;
  12. int id[10000000+5];
  13. void build_tree(int l, int r, int rt)
  14. {
  15. s[rt] = l;
  16. e[rt] = r;
  17. covered[rt] = false;
  18. int mid=(l+r)>>1;
  19. if(l != r)
  20. {
  21. build_tree(l, mid, rt << 1);
  22. build_tree(mid + 1, r, rt << 1 | 1);
  23. }
  24. }
  25. bool post(int l, int r, int rt)
  26. {
  27. if(covered[rt] == true)
  28. return false;
  29. if(s[rt] == l && e[rt] == r)
  30. {
  31. covered[rt] = true;
  32. return true;
  33. }
  34. bool res;
  35. int mid=(s[rt]+e[rt])>>1;
  36. if(r <= mid)
  37. res = post(l, r, rt << 1);
  38. else if(l > mid)
  39. res = post(l, r, rt << 1 | 1);
  40. else
  41. {
  42. bool res1 = post(l, mid, rt << 1);
  43. bool res2 = post(mid + 1, r, rt << 1 | 1);
  44. res = res1 || res2;
  45. }
  46. if(covered[rt << 1] && covered[rt << 1 | 1])
  47. covered[rt] = true;
  48. return res;
  49. }
  50. int main()
  51. {
  52. int T;
  53. scanf("%d", &T);
  54. while(T--)
  55. {
  56. scanf("%d", &n);
  57. cnt = 0;
  58. for(int i = 0; i < n; i++)
  59. {
  60. scanf("%d%d", &pl[i], &pr[i]);
  61. port[cnt++] = pl[i];
  62. port[cnt++] = pr[i];
  63. }
  64. sort(port, port + cnt);
  65. cnt = unique(port, port + cnt) - port;
  66. build_tree(1, cnt, 1);
  67. for(int i = 0; i < cnt; i++) id[port[i]] = i + 1;
  68. int res = 0;
  69. for(int i = n-1; i >= 0; i--)
  70. {
  71. if(post(id[pl[i]], id[pr[i]], 1))
  72. {
  73. res++;
  74. }
  75. }
  76. printf("%d\n", res);
  77. }
  78. return 0;
  79. }

D Count Color (POJ 2777)
题目大意:

给出一块长度为L的颜色板,将它均匀地划分为L块长度为1的小方格,从左到右标记为1、2、3、···、L。现在对这个颜色板对这个颜色板进行两种操作,第一种输入C A B C,表示在A到B这些方格中涂上颜色C,第二种输入P A B,表示查询[A,B]上所有的颜色种数。题目一共有T种颜料(1≤T≤30),分别称为颜色1,颜色2···颜色T,开始时,板子上涂有颜色1。

解题思路:

建立一棵带有lazy标记的线段树,因为最初始的状态,所有的板子都是颜色1,所以所有的标记都标记为1,在进行区间修改的过程中,如果查找的整个区间都只有一种颜色,那么,这个区间的标记就为该颜色的种类,否则,将标记传递给子节点,并取消当前节点的lazy标记。查询时,只需要查询到还有lazy标记的位置就可以了。

AC代码:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #include<cmath>
  6. using namespace std;
  7. struct node
  8. {
  9. int l,r;
  10. int num;
  11. } s[400010];
  12. int vis[35];
  13. void InitTree(int l,int r,int k)
  14. {
  15. s[k].l=l;
  16. s[k].r=r;
  17. s[k].num=1;
  18. int mid=(l+r)/2;
  19. if (l==r)
  20. return ;
  21. InitTree(l,mid,2*k);
  22. InitTree(mid+1,r,2*k+1);
  23. }
  24. void UpdataTree(int l,int r,int c,int k)
  25. {
  26. if(s[k].l==l&&s[k].r==r)
  27. {
  28. s[k].num=c;
  29. return;
  30. }
  31. if (s[k].num==c)
  32. return;
  33. if (s[k].num!=-1)//如果所查询的区间不是多种颜色
  34. {
  35. s[2*k].num=s[k].num;//更新区间的颜色
  36. s[2*k+1].num=s[k].num;
  37. s[k].num=-1;//-1表示该区间有多种颜色
  38. }
  39. int mid=(s[k].l+s[k].r)/2;
  40. if (l>mid)
  41. UpdataTree(l,r,c,2*k+1);
  42. else if (r<=mid)
  43. UpdataTree(l,r,c,2*k);
  44. else
  45. {
  46. UpdataTree(l,mid,c,2*k);
  47. UpdataTree(mid+1,r,c,2*k+1);
  48. }
  49. }
  50. void SearchTree(int l,int r,int k)
  51. {
  52. if (s[k].num!=-1)
  53. {
  54. vis[s[k].num]=1;
  55. return;
  56. }
  57. int mid=(s[k].l+s[k].r)/2;
  58. if (r<=mid)
  59. SearchTree(l,r,2*k);
  60. else if (l>mid)
  61. SearchTree(l,r,2*k+1);
  62. else
  63. {
  64. SearchTree(l,mid,2*k);
  65. SearchTree(mid+1,r,2*k+1);
  66. }
  67. }
  68. int main()
  69. {
  70. int l,t,o,ans;
  71. while (~scanf("%d%d%d",&l,&t,&o))
  72. {
  73. InitTree(1,l,1);
  74. while (o--)
  75. {
  76. char ch;
  77. int a,b,c;
  78. getchar();
  79. scanf("%c",&ch);
  80. if (ch=='C')
  81. {
  82. scanf("%d%d%d",&a,&b,&c);
  83. if (a>b)
  84. swap(a,b);
  85. UpdataTree(a,b,c,1);
  86. }
  87. else
  88. {
  89. scanf("%d%d",&a,&b);
  90. if (a>b)
  91. swap(a,b);
  92. memset(vis,0,sizeof(vis));
  93. SearchTree(a,b,1);
  94. ans=0;
  95. for (int i=1; i<=t; i++)
  96. if (vis[i]==1)
  97. ans++;
  98. printf ("%d\n",ans);
  99. }
  100. }
  101. }
  102. return 0;
  103. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注