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

题目链接

 

题意:

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

  $1\le n \le 5 \times 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+1\le\lfloor L/2\rfloor$,那么整个$[l_1,l_2]$都可以跳过。

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

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

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

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

  我们已经向目标位置(暂时未知)逼近了一部分,我们本来要在两个区间内取第$\lfloor L / 2 \rfloor + 1$小的数,现在舍弃了$s - l_1 + 1$个,就要在剩下的范围内取第$\lfloor L / 2 \rfloor - 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\n",sol(l1,r1,l2,r2,(r1-l1+r2-l2+3)>>1));
            
        }
        
    }

    return 0;

}
View Code

 

分析二:

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

  考虑优化常数。

  快读、快写、$register\;int\quad$搞上。

  成功$[TLE]\,ms\longrightarrow 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('\n');
    
}

    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

 

 

小结:

  模拟解法,分治有奇效。

  时限很紧,别忘记卡常。

 

转载于:https://www.cnblogs.com/Hansue/p/11378056.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值