[Ahoi2014]奇怪的計算器 解題報告

TA201314發表於2016-06-01

感覺這是一道非常好的題,不過我看幾乎所有人都是把它當傻逼題寫的,為出題人感到遺憾。

一個很簡單的性質是無論如何操作,每個數的相對大小是不變的,所以我們每次改變的都是一個區間。所以我們維護一個標記(k,b0,b1)

(k,b_0,b_1)
表示對這個區間裡的數x的操作為先*k,然後+b0x
+b_0x
,然後+b1
+b_1
。這樣的話對於當前在節點的標記(k,b0,b1)
(k,b_0,b_1)
,然後再加上一個新的標記(k,b0,b1)
(k',b_0',b_1')
,就變成(kk,kb0+b0,kb1+b1)
(k*k',k'*b_0+b_0',k'*b_1+b_1')
。然後再記一下每個節點最左邊和最右邊的點的值就行了。

但是這個題的關鍵是k,b0,b1

k,b_0,b_1
都可能會非常大,最大可能有109n
10^{9n}
。我看popoqqq的題解說資料很水沒有爆long long,但是我assert了一下資料發現它其實爆了,但是為什麼用int/long long寫還能a呢?
這個其實是在O(1)
O(1)
快速乘中也有所用到的看似爆掉而實際沒爆的神奇思想。
這是因為雖然k,b0,b1
k,b_0,b_1
可能很大,但我們可以讓它在模231
2^{31}
意義下存在,這樣的話我們就能求出它們運算的結果模231
2^{31}
的值,而可以預見這個結果是在[L,R]之間的,也就是231
2^{31}
以內。所以儘管我們在計算它的過程中使用了模運算,但是最終的結果其實是和不模一樣的。
唯一需要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,r
void 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)

O(1)
快速乘的思想。

相關文章