洛谷1972线段树题解

题目传送门:https://www.luogu.org/problemnew/show/P1972

菜的不行的我不会树状数组,只能拿线段树做,利用了一波1908逆序对的那个想法(不会归并排序也拿线段树做的),也就是在动态改变的时候的线段树。

读题的时候要特别注意,L 和R(1 ≤ L ≤ R ≤ N)!!!

首先我们按照右端点排序(虽然其实左右端点排序应该都可以),那么对于一个比如

6
1 2 3 4 3 5
3
1 2
3 5
2 6
的数据,对于相同的数,我们其实是只需要看右边那个的,也就是始终把右边的那个数安排进树里,在右端点不断扩大的情况下我们对于右边的数的期望始终更高,那么我们该怎么处理呢,像我这种蒟蒻一定是遍历一遍的,于是60分(4个tle呜呜呜),附上代码(代码实际上是按照左端点排序的):

#include
#include
int l[500050]={0},r[500050]={0},vis[5000050]={0},a[5000050]={0},ans[5000050]={0},num[5000050]={0};
struct seg{
     int val;
}seg[5000001];
void swap(int x,int y)
{
     int t;
     t=l[x];l[x]=l[y];l[y]=t;
     t=r[x];r[x]=r[y];r[y]=t;
     t=num[x];num[x]=num[y];num[y]=t;
     return;
}
void swap2(int x,int y)
{
     int t;
     t=ans[x];ans[x]=ans[y];ans[y]=t;
     t=num[x];num[x]=num[y];num[y]=t;
     return;
}
int quicksort(int left,int right)
{
    int i,j,mid;
    i=left;j=right;mid=l[(i+j)/2];
    while(i<=j)
    {
    while(l[i]mid) j--;
    if(i<=j)
    {
    swap(i,j);
    i++;j--;
    }
    }
    if(ileft)   quicksort(left,j);
    return 0;
}
int quicksort2(int left,int right)
{
    int i,j,mid;
    i=left;j=right;mid=num[(i+j)/2];
    while(i<=j)
    {
    while(num[i]mid) j--;
    if(i<=j)
    {
    swap2(i,j);
    i++;j--;
    }
    }
    if(ileft)   quicksort2(left,j);
    return 0;
}
int update(int root,int nstart,int nend,int index,int add)
{
    int mid;
    if(nstart==nend)
    {
      if(nstart==index)
        if(add==1)
        seg[root].val=1;
        else seg[root].val=0;
      return 0;
    }
    mid=(nstart+nend)/2;
    if(index<=mid)  update(root*2,nstart,mid,index,add);
    else  update(root*2+1,mid+1,nend,index,add);
    seg[root].val=seg[root*2].val+seg[root*2+1].val;
    return 0;
}
int quire(int root,int nstart,int nend,int qstart,int qend)
{
    int mid;
    if(nstart>qend||qstart>nend)
      return 0;
    if(qstart<=nstart&&qend>=nend)
      return seg[root].val;
    mid=(nstart+nend)/2;
    return quire(root*2,nstart,mid,qstart,qend)+quire(root*2+1,mid+1,nend,qstart,qend);
}
int main()
{
    int n,m,i,j,max=0;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
      scanf("%d",&a[i]);
    scanf("%d",&m);
    for(i=1;i<=m;i++)
    {
      scanf("%d %d",&l[i],&r[i]);
      num[i]=i;
    }
    quicksort(1,m);
    for(i=l[1];i<=r[1];i++)
     if(vis[a[i]]==0)
     {
      update(1,1,n,i,1);
      vis[a[i]]=1;
     }
    ans[1]=quire(1,1,n,l[1],r[1]);
    max=r[1];
    for(i=2;i<=m;i++)
    {
    if(l[i]!=l[i-1])
      for(j=l[i-1];j

那么我究竟在做什么蠢事呢,当然是我在疯狂遍历l[i]到r[i]使他做出了很多无意义的循环,例如(以下先暂时以左端点排序):

当数据为求1到4,2到5,3到6,我会把他们全部遍历但其实这是不必要的,我当时的思路是,如果不遍历,我会漏掉在l[i]到r[i-1]中与l[i-1]到l[i]中删除的元素重复的元素,但实际上我只需要把一个元素加入线段树同时删除上一个与这个元素相同的元素,利用一个b数组来记录上一个元素的根节点(b[i]自然就是没有上一个相同的,这个想法有一点前向星的味道,当然只有韩哥哥思路这么灵活,尽管他什么都没学过却什么都能想到),附上代码:

#include
#include
int l[500050]={0},r[500050]={0},a[5000050]={0},ans[5000050]={0},num[5000050]={0},b[100001]={0};
struct seg{
     int val;
}seg[5000001];
void swap(int x,int y)
{
     int t;
     t=num[x];num[x]=num[y];num[y]=t;
     return;
}
int quicksort(int left,int right)
{
    int i,j,mid;
    i=left;j=right;mid=r[num[(i+j)/2]];
    while(i<=j)
    {
    while(r[num[i]]mid) j--;
    if(i<=j)
    {
    swap(i,j);
    i++;j--;
    }
    }
    if(ileft)   quicksort(left,j);
    return 0;
}
int update(int root,int nstart,int nend,int index,int add)
{
    int mid;
    if(nstart==nend)
    {
      if(nstart==index)
        if(add==1)
        seg[root].val=1;
        else seg[root].val=0;
      return 0;
    }
    mid=(nstart+nend)/2;
    if(index<=mid)  update(root*2,nstart,mid,index,add);
    else  update(root*2+1,mid+1,nend,index,add);
    seg[root].val=seg[root*2].val+seg[root*2+1].val;
    return 0;
}
int quire(int root,int nstart,int nend,int qstart,int qend)
{
    int mid;
    if(nstart>qend||qstart>nend)
      return 0;
    if(qstart<=nstart&&qend>=nend)
      return seg[root].val;
    mid=(nstart+nend)/2;
    return quire(root*2,nstart,mid,qstart,qend)+quire(root*2+1,mid+1,nend,qstart,qend);
}
int main()
{
    int n,m,i,j,max=0,t,t1=1,p,left,right,mid;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
      scanf("%d",&a[i]);
    scanf("%d",&m);
    for(i=1;i<=m;i++)
    {
      scanf("%d %d",&l[i],&r[i]);
      num[i]=i;
    }
    quicksort(1,m);//右端点按小到大排列 
    for(i=1;i<=n;i++)
	  {t=a[i];
	   if(b[t]!=0)//利用之前记录下a[i]的根节点来删除它 
	     {
          j=b[t];
	      while(j!=0)
	        {
             seg[j].val--;
	         j=j/2;//底下的反向操作,一路上经过的节点都减一 
            }
         }
	   p=1;
	   left=1;
	   right=n;
	   while(left<=right)
		 {
          seg[p].val++;//将一路上经过的节点都加一 
		  if(left==right) {b[t]=p;break;}//记录下a[i]的节点 
		  mid=(left+right)/2;
		  if(i<=mid) {right=mid;p=2*p;}
		  else {left=mid+1;p=2*p+1;} 
         }
       while(r[num[t1]]==i)//利用(1 ≤ L ≤ R ≤ N)  
	     {ans[num[t1]]=quire(1,1,n,l[num[t1]],i);
		  t1++;}
	   if(t1>m) break;
    }
    for(i=1;i<=m;i++)
      printf("%d\n",ans[i]);
    return 0;
}

 

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