题意:有一个沙漏由A,B两个杯子组成。一共有X克沙子。
每秒会有一克沙子从上杯子流到下杯子,直到流完为止。

一共会有k次翻转杯子,第i次翻转杯子会在第 $r_i$ 秒。

有q组询问,每组形如 $t,a$ ,表示一开始A杯子在上,里面有a克沙子(B杯子里有 $X-a$ 克沙子),求第t秒的时候A杯子里有多少沙子。
$q,k\le 10^5$


$q,k\le 10^5$ ,暴力是一定会超时的。所以我们要想想办法。

计算当一开始里A杯子有s克沙子杯子里有$l$克沙子的时候第$r_i$秒时A里面的沙子量。

可以发现:1的时候为$\max\{s-r_1,0\}$,等等。

然后会发现每一次A里面的沙子量可以表示为$\max\{\min\{s+a,b\},c\}$,其中$a,b,c$都是常数,

所以我们可以找一波递推公式。

用$(a,b,c)$表示$\max\{\min\{s+a,b\},c\}$。
设每次流沙子的时间为d。
发现当第奇数次翻转的时候,沙子量会从$(a,b,c)$变为$\max\{\max\{\min\{s+a,b\},c\}-d,0\}$,第偶数次的时候会变成$\min\{\max\{\min\{s+a,b\},c\}+d,l\}$,暴力拆一波式子得到第奇数次翻转的时候变为$(a-d,b-d,\max\{c-d,0\})$,第偶数次翻转的时候变为$(a+d,min\{\max\{b,c\}+d,l\},\min\{c+d,l\})$。

求出每一次的a,b,c以后,对每组询问二分查找一下在第t秒之前最后一次翻转,这次翻转之前A杯子里有多少沙子,然后再一波$O(1)$计算一下就可以了(毕竟没有翻转直接流当然$O(1)$)

/*
Author: CNYALI_LK
LANG: C++
PROG: F.cpp
*/
#include<bits/stdc++.h>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define all(x) x.begin(),x.end()
using namespace std;
const double eps=1e-8;
const double PI=acos(-1.0);
typedef long long ll;
template<class T>void chkmin(T &a,T b){a=a<b?a:b;}
template<class T>void chkmax(T &a,T b){a=a>b?a:b;}
template<class T>T sqr(T a){return a*a;}
template<class T>T mmin(T a,T b){return a<b?a:b;}
template<class T>T mmax(T a,T b){return a>b?a:b;}
template<class T>T aabs(T a){return a<0?-a:a;}
int r[102424];
int t[102424],y[102424];
int a[102424],b[102424],c[102424];
#define min mmin
#define max mmax
#define abs aabs
int main(){
#ifdef cnyali_lk
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif
    int l,k,q,d;
    a[0]=0;
    b[0]=0x3f3f3f3f;
    c[0]=-0x3f3f3f3f;
    scanf("%d%d",&l,&k);
    for(int i=1;i<=k;++i)scanf("%d",r+i);
    a[1]=(-r[1]);
    b[1]=0x3f3f3f3f;
    c[1]=0;
    for(int i=2;i<=k;++i){
        d=r[i]-r[i-1];
        if(i&1){
            a[i]=a[i-1]-d;
            b[i]=b[i-1]-d;
            c[i]=mmax(c[i-1]-d,0);
        }
        else{
            a[i]=a[i-1]+d;
            b[i]=mmin(mmax(b[i-1],c[i-1])+d,l);
            c[i]=min(c[i-1]+d,l);
        }
    }
    scanf("%d",&q);
    for(int i=1;i<=q;++i){
        scanf("%d",t+i);
        int lx,rx,mid;
        scanf("%d",y+i);
        lx=0;rx=k;
        while(lx<=rx){
            mid=(lx+rx)>>1;
            if(t[i]>=r[mid])lx=mid+1;
            else rx=mid-1;
        }
        --lx;
        t[i]-=r[lx];
        y[i]=max(min(y[i]+a[lx],b[lx]),c[lx]);
        if(lx&1){
            y[i]=min(y[i]+t[i],l);
        }
        else{
            y[i]=max(y[i]-t[i],0);
        }
        printf("%d\n",y[i]);
    }
    return 0;
}

标签: 二分, 推公式

添加新评论