• JZOJ.5246【NOIP2017模拟8.8】Trip


    Description

           多年之后,worldwideD厌倦竞争,隐居山林。
           他的家乡开始发展起了旅游业,在一条很长的主干道上,有N个旅游景点,按顺序编号为1到N。根据游客们网上的评分,第i个景点有一个评估值a[i],为了区分开不同的景点,评估值是两两不同的。
           今天有M组游客前来旅游,第i组游客选择遍历景点Li到景点Ri这一段路。他们搜到Li到Ri的所有评估值,如果对于景点j(Li≤j≤Ri),不存在景点x(Li≤x<j)满足a[x]>a[j]或不存在景点y(j<y≤Ri)满足a[y]>a[j],那么他们会进入景点j。
           现在worldwideD想知道,每组游客会去多少个景点。
     

    Input

    第一行两个整数N,M,意义见题面。
    接下来一行N个整数,第i个是a[i],意义见题面。
    接下来M行,第i行两个整数Li,Ri,意义见题目。

    Output

    M行,第i行表示第i组游客去的景点个数。
     

    Sample Input

    6 4
    3 1 7 4 5 2
    1 5
    2 5
    2 6
    4 6

    Sample Output

    3
    3
    4
    3
     

    Data Constraint

    30%:N,M≤5,000
    60%:N,M≤100,000
    100%:N,M≤1,000,000   0≤|a[i]|≤1,000,000,000  1≤Li≤Ri≤N
     

    Hint

    第一组游客选择路段的景点评估值序列为[3,1,7,4,5],其中3,7,5满足条件
    第二组游客选择路段的景点评估值序列为[1,7,4,5],其中1,7,5满足条件
    第三组游客选择路段的景点评估值序列为[1,7,4,5,2],其中3,7,5,2满足条件
    第四组游客选择路段的景点评估值序列为[4,5,2],其中4,5,2满足条件
    本题数据规模较大,请注意您的常数造成的影响。
    在这里给出一种输入输出优化的模板,在主程序中直接调用read()即可读入一个整数,调用write(x)可以把一个int变量x输出并换行。
    int read()
    {
           int x=0,sig=1;
           char c;
           for (c=getchar();c<'0' || c>'9';c=getchar()) if (c=='-') sig=-1;
           for (;c>='0' && c<='9';c=getchar()) x=x*10+c-48;
           return x*sig;
    }
    void write(int x)
    {
           if (!x) putchar('0');else
           {
                  char s[10];
                  int i,j=0;
                  for (;x>0;x/=10) s[j++]=x%10;
                  for (i=j-1;i>=0;i--) putchar(s[i]+48);
           }
           putchar(' ');
    }

     考虑30分的可以拿个单调栈向左扫一遍向右扫一遍再将个数加起来再减去1即可。

    这道题是要求我们判断这个景点的评估值在给定的子区间里是否有比它大值存在,如果一边没有大于它的存在,则它就是旅客会前往的景点。

    既需要位置关系又需要大小关系,我们考虑大根笛卡尔树。

    笛卡尔树是一种同时满足二叉搜索树和堆的性质的数据结构。

    它的中序遍历的序列为原数组序列。

    树中节点的值大于其左右子节点的值。

    建树很简单, 用个单调栈O(n)即可建好。我们很容易发现一些性质:

    1.对于一个询问L,R它的答案只会出现在笛卡尔树的路径上。

    2.L,R的LCA中,从L到LCA路径上,有一个点是其父亲的左孩子答案就加一,R到LCA上,有一个点是其父亲的右孩子答案就加一。

    我们知道笛卡尔树上一个点A是其父亲B的左孩子表明A在它父亲B的左边,且权值小于父亲B,对于LLCA路径(也就是区间L-LCA)中而言这意味着A的左边没有比它大的点(如果有,那么A应该会在比它大的那个点C的右边),于是就对答案有1的贡献,虽然右边有比它大的点(父亲B);对于RLCA路径(也就是区间LCA-R)中则相反。这就很好的符合题目的性质,我们就可以用笛卡尔树解决这道题了。

    我们就可以用tarjan求出LCA,然后预处理下每个点到根节点的路径上有多少个点是其父亲的左孩子和右孩子,然后计算出答案即可。时间复杂度 O(NαN)

      1 #include<iostream>
      2 #include<cstring>
      3 #include<cstdio>
      4 #define N 3000002
      5 using namespace std;
      6 int n,m,t,zhan[N],len,num,ans[N],f[N],u,v,head[N],root,visit[N];
      7 struct data1{
      8     int l,r,p,v,lnum,rnum;
      9     void init(){
     10         l=0,r=0,p=0,v=0,lnum=0,rnum=0;
     11     }
     12 }tree[N];
     13 struct data2{
     14     int next,to,sign;
     15 }line[N];
     16 int read()
     17 {
     18        int x=0,sig=1;
     19        char c;
     20        for (c=getchar();c<'0' || c>'9';c=getchar()) if (c=='-') sig=-1;
     21        for (;c>='0' && c<='9';c=getchar()) x=x*10+c-48;
     22        return x*sig;
     23 }
     24 void write(int x)
     25 {
     26        if (!x) putchar('0');else
     27        {
     28               char s[10];
     29               int i,j=0;
     30               for (;x>0;x/=10) s[j++]=x%10;
     31               for (i=j-1;i>=0;i--) putchar(s[i]+48);
     32        }
     33        putchar('
    ');
     34 }
     35 void add(int u,int v){
     36     num++;
     37     line[num].next=head[u];
     38     line[num].to=v;
     39     line[num].sign=num;
     40     head[u]=num;
     41     num++;
     42     line[num].next=head[v];
     43     line[num].to=u;
     44     line[num].sign=num;
     45     head[v]=num;
     46 }
     47 int build(){     //建笛卡尔树 
     48     len=1;
     49     zhan[1]=1;
     50     for (int i=2;i<=n;i++){
     51         while ((len>0)&&(tree[zhan[len]].v<tree[i].v)) len--;
     52         if (len){
     53             tree[i].p=zhan[len];
     54             tree[tree[zhan[len]].r].p=i;
     55             tree[i].l=tree[zhan[len]].r;
     56             tree[zhan[len]].r=i;
     57         }
     58         else {
     59             tree[zhan[1]].p=i;
     60             tree[i].l=zhan[1];
     61         }
     62         zhan[++len]=i;    
     63     }
     64     return zhan[1];
     65 }
     66 int find(int x){
     67     if (f[x]==x) return x;
     68     f[x]=find(f[x]);
     69     return f[x];
     70 }
     71 void tarjan(int x,int ln,int rn){   //Tarjan求LCA 
     72     f[x]=x;
     73     visit[x]=1;
     74     tree[x].lnum=ln;
     75     tree[x].rnum=rn;
     76     if (tree[x].l) {
     77         tarjan(tree[x].l,ln+1,rn);
     78         f[tree[x].l]=x;
     79     }
     80     if (tree[x].r) {
     81         tarjan(tree[x].r,ln,rn+1);
     82         f[tree[x].r]=x;
     83     }
     84     int v=0,e=0;
     85     for (int i=head[x];i!=0;i=line[i].next){
     86         v=line[i].to;
     87         if (visit[v]){e=find(v);
     88         if (line[i].sign&1) ans[(line[i].sign+1)/2]=tree[x].lnum-tree[e].lnum+tree[v].rnum-tree[e].rnum+1;
     89         else ans[line[i].sign/2]=tree[v].lnum-tree[e].lnum+tree[x].rnum-tree[e].rnum+1;
     90         }
     91     }
     92 }
     93 int main(){
     94     freopen("trip.in","r",stdin);
     95     freopen("trip.out","w",stdout);    
     96     memset(visit,0,sizeof(visit));
     97     n=read();
     98     m=read();
     99     for (int i=1;i<=n;i++){
    100         tree[i].init();
    101         tree[i].v=read();
    102     }
    103     root=build();
    104     num=0;
    105     for (int i=1;i<=m;i++){
    106         u=read();
    107         v=read();
    108         add(u,v);
    109     }
    110     tarjan(root,0,0);
    111     for (int i=1;i<=m;i++)
    112      write(ans[i]);
    113     return 0;
    114 }
    神奇的代码

    联想很重要

  • 相关阅读:
    第三次作业
    第二次作业
    第一次作业
    软件工程第0次作业
    第四次作业
    第三次作业
    第二次作业
    第一次作业
    第零次作业
    第四次软件工程作业
  • 原文地址:https://www.cnblogs.com/Lanly/p/7309433.html
Copyright © 2020-2023  润新知