[Ahoi2014]奇怪的计算器 解题报告

来源:互联网 发布:volt模板引擎 php 编辑:程序博客网 时间:2024/06/05 06:51

感觉这是一道非常好的题,不过我看几乎所有人都是把它当傻逼题写的,为出题人感到遗憾。

一个很简单的性质是无论如何操作,每个数的相对大小是不变的,所以我们每次改变的都是一个区间。所以我们维护一个标记(k,b0,b1)表示对这个区间里的数x的操作为先*k,然后+b0x,然后+b1。这样的话对于当前在节点的标记(k,b0,b1),然后再加上一个新的标记(k,b0,b1),就变成(kk,kb0+b0,kb1+b1)。然后再记一下每个节点最左边和最右边的点的值就行了。

但是这个题的关键是k,b0,b1都可能会非常大,最大可能有109n。我看popoqqq的题解说数据很水没有爆long long,但是我assert了一下数据发现它其实爆了,但是为什么用int/long long写还能a呢?
这个其实是在O(1)快速乘中也有所用到的看似爆掉而实际没爆的神奇思想。
这是因为虽然k,b0,b1可能很大,但我们可以让它在模231意义下存在,这样的话我们就能求出它们运算的结果模231的值,而可以预见这个结果是在[L,R]之间的,也就是231以内。所以尽管我们在计算它的过程中使用了模运算,但是最终的结果其实是和不模一样的。
唯一需要long long的地方只在于找到>R的非法区间,其他的变量其实我们都用int,令其自然溢出即可。

#include<cstdio>#include<cstring>#include<cmath>#include<iostream>using namespace std;#include<cstdlib>#include<algorithm>const int N=1e5+5,Q=1e5+5;typedef long long LL;int L,R;char * cp=(char *)malloc(3000000);void in(int &x){    while(*cp<'0'||*cp>'9')++cp;    for(x=0;*cp>='0'&&*cp<='9';)x=x*10+(*cp++^'0');}void in(char &c){    while(*cp!='+'&&*cp!='-'&&*cp!='*'&&*cp!='@')++cp;    c=*cp++;}char * os=(char *)malloc(2000000),*op=os;void out(int x){    if(x){        out(x/10);        *op++='0'+x%10;    }}struct OS{    char opt;    int a;}order[N];struct QS{    int x;    int i;    bool operator < (const QS & o)const{        return x<o.x;    }}a[Q];int ans[Q];struct SS{    int k,b0,b1;    int l,r;}segt[Q<<2];#define lson node<<1,l,l+r>>1#define rson node<<1|1,(l+r>>1)+1,rvoid out(int node,int l,int r){    printf("%d[%d,%d]={k=%d,b0=%d,b1=%d,l=%d,r=%d}\n",node,l,r,segt[node].k,segt[node].b0,segt[node].b1,segt[node].l,segt[node].r);}void paint(int node,int l,int r,int k,int b0,int b1){    //printf("paint(%d,%d,%d,%I64d,%I64d,%I64d)\n",node,l,r,k,b0,b1);    segt[node]=(SS){segt[node].k*k,segt[node].b0*k+b0,segt[node].b1*k+b1,segt[node].l*k+b0*a[l].x+b1,segt[node].r*k+b0*a[r].x+b1};}void pushdown(int node,int l,int r){    if(segt[node].k!=1||segt[node].b0||segt[node].b1){        paint(lson,segt[node].k,segt[node].b0,segt[node].b1),paint(rson,segt[node].k,segt[node].b0,segt[node].b1);        segt[node].k=1,segt[node].b0=segt[node].b1=0;    }}void pushup(int node){    segt[node].l=segt[node<<1].l;    segt[node].r=segt[node<<1|1].r;    //printf("%d:%I64d,%I64d\n",node,segt[node<<1].l,segt[node<<1|1].r);}int cal(int data,int x,int k,int b0,int b1){    return min((LL)k*data+(LL)b0*x+b1,R+1LL);}void build(int node,int l,int r){    segt[node].k=1,segt[node].b0=segt[node].b1=0;    if(l==r)segt[node].l=segt[node].r=a[l].x;    else{        build(lson),build(rson);        pushup(node);    }}void rquery(int node,int l,int r,int k,int b0,int b1){    //printf("rquery(%d,[%d,%d],%d,%d,%d)\n",node,l,r,k,b0,b1);    //printf("cal(%d)=%d\n",l,cal(segt[node].l,a[l].x,k,b0,b1));    if(cal(segt[node].r,a[r].x,k,b0,b1)<=R)paint(node,l,r,k,b0,b1);    else        if(cal(segt[node].l,a[l].x,k,b0,b1)>R)paint(node,l,r,0,0,R);        else{            pushdown(node,l,r);            if(cal(segt[node<<1].r,a[l+r>>1].x,k,b0,b1)>R){                paint(rson,0,0,R);                rquery(lson,k,b0,b1);            }            else{                paint(lson,k,b0,b1);                rquery(rson,k,b0,b1);            }            pushup(node);        }    //out(node,l,r);}void lquery(int node,int l,int r,int k,int b0,int b1){    if(cal(segt[node].l,a[l].x,k,b0,b1)>=L)paint(node,l,r,k,b0,b1);    else        if(cal(segt[node].r,a[r].x,k,b0,b1)<L)paint(node,l,r,0,0,L);        else{            pushdown(node,l,r);            if(cal(segt[node<<1|1].l,a[(l+r>>1)+1].x,k,b0,b1)<L){                paint(lson,0,0,L);                lquery(rson,k,b0,b1);            }            else{                paint(rson,k,b0,b1);                lquery(lson,k,b0,b1);            }            pushup(node);        }    //out(node,l,r);}void query(int node,int l,int r){    if(l==r)ans[a[l].i]=segt[node].l;    else{        pushdown(node,l,r);        query(lson),query(rson);    }}int main(){    freopen("calc8.in","r",stdin);    freopen("bzoj_3878.out","w",stdout);    fread(cp,1,3000000,stdin);    int n;    in(n),in(L),in(R);    for(int i=1;i<=n;++i)in(order[i].opt),in(order[i].a);    int q;    in(q);    for(int i=1;i<=q;++i){        in(a[i].x);        a[i].i=i;    }    sort(a+1,a+q+1);    build(1,1,q);    for(int i=1;i<=n;++i)        switch(order[i].opt){            case '+':                rquery(1,1,q,1,0,order[i].a);                break;            case '-':                lquery(1,1,q,1,0,-order[i].a);                break;            case '*':                rquery(1,1,q,order[i].a,0,0);                break;            case '@':                rquery(1,1,q,1,order[i].a,0);                break;        }    query(1,1,q);    for(int i=1;i<=q;++i){        if(ans[i])out(ans[i]);        else *op++='0';        *op++='\n';    }    fwrite(os,1,op-os,stdout);}

总结:
①一定要算好量的范围!!
②对于中间量可能很大,结果量很小的情况,我们可以在模意义下去运算它。——参考O(1)快速乘的思想。

0 0
原创粉丝点击