• 【纪中集训2019.08.18】【JZOJ6308】中间值


    题目链接

    题意:

      有两段长度为$n$的不严格单调上升序列$a$和$b$,有两种操作共$m$次。单点赋值为$x$,保证不破坏序列不严格单调上升的性质;询问$a$的$[l_1,r_1]$区间和$b$的$[l_2,r_2]$区间合并后的中间值,保证合并后的区间长度为奇数。

      $1le n le 5 imes 10^5 , ; 1 le m le 10^6 , ; 0 le a_i , b_i , x le 10^9 , ; 1 le l_1 le r_1 le n , ; 1 le l_2 le r_2 le n$

    分析一:

      考虑暴力怎么做。

      设立个区间指针,一个计数器,每次跳一个指针,计数器$++$,可以在$O(n)$内处理一个询问。

      或者按照类似于归并排序的方法把两个区间写到新的数组里,再取中间位置。

      但仔细思考一下,发现上述两种暴力在实现细节上是一模一样的,都是比较两个头部,向目标位置逼近;也就是说,“合并”和“中间值”并不是什么重要的东西,询问合并有序区间第$k$大的都是同一种做法。我们的问题在于取数。

      怎么样优化这个取数?

      一个很简单的贪心:设区间长度和为$L$,如果$a[r_1]<b[l_2]$并且$r_1-l_1+1lelfloor L/2 floor$,那么整个$[l_1,r_1]$都可以跳过。

      特殊情况毕竟少见,思考一下能不能扩大这种方法的适用面。

      很容易发现,如果$a[s]<b[t]$并且$s-l_1+1lelfloor L/2 floor$,那么整个$[l_1,s]$都可以跳过。

      那么这个$s$和$t$怎么定?

      让我们暂时转换一下方向,跳过了一段区间之后,会发生什么?

      我们已经向目标位置(暂时未知)逼近了一部分,我们本来要在两个区间内取第$lfloor L / 2 floor + 1$小的数,现在舍弃了$s - l_1 + 1$个,就要在剩下的范围内取第$lfloor L / 2 floor - s + l_1$小的数,成功地缩小了问题规模。

      这就是分治嘛!

      那我们直接一半一半砍下去,分到两个区间上比较(注意不能越界),既不会出错(拼得区间长度不超过$k$),也会让问题规模下降得很快(两边都取了尽可能大的数)。

      即,设当前要取的是区间第$k$小的数,就将$k$折半,放在两个区间的对应位置$s,t$上(注意不能越界),比较$a_s,b_t$。不妨设$a_s<b_t$,那么我们可以递归地在区间$[s+1,r_1]$和$[l_2,r_2]$上找第$k-(s-l_1+1)$小的数(因为答案一定不会出现在$[l_1,s]$中)。

      这样我们的时间复杂度就是$O(m;log\,n)$的,可以通过。

      但容易发现:这个时间复杂度实际上很紧凑,很容易跑满,对于题目所给数据规模,其实十分危险。

      更新(2019.08.19  20:14):递归写法自带两倍常数,这个应该是重要原因(之一)。

      因此,如果不加优化,交上去之后,纪中的“更慢OJ”会神秘兮兮地告诉你:“我看你脸色行事”。

    实现一(70~100分):

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define IL inline
    using namespace std;
    const int N=5e5;
    
        int n,m,a[N+3],b[N+3];
        
    IL int find(int *a,int l,int r,int k){
        return (l+k-1)>r?r:(l+k-1);
        
    }
        
    int sol(int l1,int r1,int l2,int r2,int k){
        if(l1>r1)
            return b[l2+k-1];
        if(l2>r2)
            return a[l1+k-1];
        if(k==1)
            return min(a[l1],b[l2]);
        
        int x=find(a,l1,r1,k>>1);
        int y=find(b,l2,r2,k>>1);
        if(a[x]<b[y])
            return sol(x+1,r1,l2,r2,k-(x-l1+1));
        return sol(l1,r1,y+1,r2,k-(y-l2+1));
        
    } 
    
    int main(){
        freopen("median.in","r",stdin);
        freopen("median.out","w",stdout);
    
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            scanf("%d",&b[i]);
            
        for(int i=1;i<=m;i++){
            int opt;
            scanf("%d",&opt);
            if(opt==1){
                int x,y,z;
                scanf("%d%d%d",&x,&y,&z);
                if(x==0)
                    a[y]=z;
                else     
                    b[y]=z;
                
            }
            else {
                int l1,r1,l2,r2;
                scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
                printf("%d
    ",sol(l1,r1,l2,r2,(r1-l1+r2-l2+3)>>1));
                
            }
            
        }
    
        return 0;
    
    }
    View Code

    分析二:

      好像结构上并不能做什么优化。

      考虑优化常数。

      快读、快写、$register;intquad$搞上。

      成功$[TLE]\,mslongrightarrow 637ms$,跻身最优解$Rank13$。(2019.08.17  19:56)

    实现二(100分):

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define IL inline
    #define RI register int
    using namespace std;
    const int N=5e5;
    
    IL bool num(char ch){
        return '0'<=ch&&ch<='9';
        
    }
    
    IL void read(int &x){
        char ch;
        while(!num(ch=getchar()));
        
        x=ch-'0';
        while(num(ch=getchar()))
            x=(x<<3)+(x<<1)+ch-'0';
        
    }
    
    IL void write(int x){
        int s[13],k=0;
        while(x>0){
            s[++k]=x%10;    x/=10;
            
        }
        
        do
            putchar(s[k]+'0');
        while(--k);
        
    }
    
    IL void read(int &p1,int &p2){
        read(p1);    read(p2);
        
    }
    
    IL void read(int &p1,int &p2,int &p3){
        read(p1);    read(p2);    read(p3);
        
    }
    
    IL void read(int &p1,int &p2,int &p3,int &p4){
        read(p1);    read(p2);    read(p3);    read(p4);
        
    }
    
    IL void writeln(int x){
        write(x);    putchar('
    ');
        
    }
    
        int n,m,a[N+3],b[N+3];
        
    IL int find(int *a,int l,int r,int k){
        return (l+k-1)>r?r:(l+k-1);
        
    }
        
    int sol(int l1,int r1,int l2,int r2,int k){
        if(l1>r1)
            return b[l2+k-1];
        if(l2>r2)
            return a[l1+k-1];
        if(k==1)
            return min(a[l1],b[l2]);
        
        int x=find(a,l1,r1,k>>1);
        int y=find(b,l2,r2,k>>1);
        if(a[x]<b[y])
            return sol(x+1,r1,l2,r2,k-(x-l1+1));
        return sol(l1,r1,y+1,r2,k-(y-l2+1));
        
    } 
    
    int main(){
        freopen("median.in","r",stdin);
        freopen("median.out","w",stdout);
    
        read(n,m);
        for(RI i=1;i<=n;i++)
            read(a[i]);
        for(RI i=1;i<=n;i++)
            read(b[i]);
            
        int opt,p1,p2,p3,p4;
        for(RI i=1;i<=m;i++){
            int opt;    read(opt);
            if(opt==1){
                read(p1,p2,p3);
                if(p1==0)
                    a[p2]=p3;
                else     
                    b[p2]=p3;
                
            }
            else {
                read(p1,p2,p3,p4);
                writeln(sol(p1,p2,p3,p4,(p2-p1+p4-p3+3)>>1));
                
            }
            
        }
    
        return 0;
    
    }
    View Code

    小结:

      模拟解法,分治有奇效。

      时限很紧,别忘记卡常。

  • 相关阅读:
    正确使用SqlConnection对象,兼谈数据库连接池
    简单设计实现基于Forms认证的注册登录等用户基础服务
    简单利用Memcached进行缓存层设计
    殊途同归,ado.net快速实现MySql的CRUD
    【数据库设计】“Max加一”生成主键的注意点
    利用FastReflectionLib快速实现对象克隆
    容易遗忘的一些小代码之 Cross apply and Outer apply
    OBJECT_ID 有哪些种类
    BIWORK 分区表阅读与实践笔记
    容易遗忘的一些小代码之 Merge Operation and Output Clause
  • 原文地址:https://www.cnblogs.com/Hansue/p/11378056.html
Copyright © 2020-2023  润新知