zoukankan      html  css  js  c++  java
  • P4433 [COCI2009-2010#1] ALADIN

    题目描述

    给你 n 个盒子,有 q 个操作,操作有两种:

    • 第一种操作输入格式为"1 L R A B",表示将编号为LR的盒子里的石头数量变为(X−L+1)×A mod B,其中 X为盒子的编号。

    • 第二种操作输入格式为"2 L R",表示查询编号为LR的盒子里的石头总数。

    输入输出格式

    输入格式:

    第一行有两个数字n(1≤N≤109),q(1≤Q≤50000)

    接下来 q行表示询问操作。

    输出格式:

    对于每个第二种操作,输出石头总数。

    输入输出样例

    输入样例#1: 
    6 3
    2 1 6
    1 1 5 1 2
    2 1 6
    
    输出样例#1: 
    0
    3
    输入样例#2: 
    4 5
    1 1 4 3 4
    2 1 1
    2 2 2
    2 3 3
    2 4 4
    2 5 5
    
    输出样例#2: 
    3
    2
    1
    0
    输入样例#3: 
    4 4
    1 1 4 7 9
    2 1 4
    1 1 4 1 1
    2 1 4
    
    输出样例#3: 
    16
    0

    说明

     

    Solution:

      本题黑的不行啊,两天就荒(废)在这题上了!

      思路:数学大套路+线段树。

      题目中唯一出现的数学式子:$sum_limits{i=1}^{ileq n} {(i*Amod B)}$,那么切入点当然是如何快速求该式子咯。

      我们对式子变形:原式$=A*sum_limits{i=1}^{ileq n}{i}-B*sum_limits{i=1}^{ileq n}{lfloor frac{i*A}{B} floor}$。

      被减数式子很好算直接忽略,减数式子的解决关键是式子$sum_limits{i=1}^{ileq n}{lfloor frac{i*A}{B} floor}$,对此式子我们分情况讨论($A==B$的情况该式子直接算,所以忽略咯):

        1、$A>B$,我们假设$A=kB+r$,则原式子化为$sum_limits{i=1}^{ileq n}{lfloor frac{i*(kB+r)}{B} floor}=k*sum_limits{i=1}^{ileq n}{i}+sum_limits{i=1}^{ileq n}{lfloor frac{i*r}{B} floor}$,我们把$r$当作新的$A$,那么就将该式子转化为了$A<B$的情况,于是关键就成了$A<B$时如何快速求原式子。

        2、$A<B$,我们将其抽象到平面直角坐标系上,不难发现$sum_limits{i=1}^{ileq n}{lfloor frac{i*A}{B} floor}$实际求的是坐标为$(0,0),(n,0),(n,frac{n*A}{B})$三点围成的三角形的不在$X$轴上的格点个数,可能有点难以理解,我们画图理解(留图待画、手绘勿喷):

    如图,对角线上每个被标记的点到x轴的垂线段上的格点(除开x轴的格点),所对应的就是每个$lfloor frac{i*A}{B} floor$。我们若直接算下三角的格点个数会很麻烦,但是很容易算出整个矩形的格点个数,我们设$m=lfloor frac{n*A}{B} floor$,则矩形的格点个数为$n*m$,我们用矩形的格点个数-上三角的格点个数+对角线上的格点个数,就能得到原式子的值。如何求上三角的格点个数和对角线的格点个数呢?我们把上三角逆时针旋转90度,就能得到一个类似于下三角的一条边为整数的三角形,用同样的方法去求,发现上三角的格点个数恰好等于$sum_limits{i=1}^{ileq m}{lfloor frac{i*B}{A} floor}$,因为$A<B$,我们又回到了第1种$A>B$情况,于是可以递归去求(递归边界就是$A|B$返回0)。而对角线斜率为$frac{frac{A}{gcd(A,B)}}{frac{B}{gcd(A,B)}}$,那么横坐标每隔$frac{B}{gcd(A,B)}$个单位会有一个格点出现,所以对角线上共有$frac{n*gcd(A,B)}{B}$个格点。不难发现整个递归过程就是个类欧几里得的求法,时间复杂度为$O(log n)$

      有了上面的结论,我们就能在$O(log n)$的复杂度下修改一段区间,那么对于原题的区间查询,我们使用懒惰标记,记录每段被修改的$A,B$和前一个点位置$st$,然后任意一度区间$[l,r]$的和都可以用$sum[r]-sum[l-1]$去算,而每个$sum[i]$直接调用上面的递归过程就好了。

      细节太多,注意:区间肯定得离散,而求区间和时用到了前缀和的思想,一个简单的离散方法是对询问的$l,r$,将$l-1,r$离散,然后线段树建树时每个节点维护的是一整段区间,要把每段小的区间都表示出来(开始30分的原因)。

      最后总时间复杂度$O(qlog^2 n)$,稳妥!(>.^_^.<咕咕)

      

    代码:

    /*Code by 520 -- 9.7*/
    #include<bits/stdc++.h>
    #define il inline
    #define ll long long
    #define RE register
    #define For(i,a,b) for(RE int (i)=(a);(i)<=(b);++(i))
    #define Bor(i,a,b) for(RE int (i)=(b);(i)>=(a);--(i))
    #define lson l,m,rt<<1
    #define rson m,r,rt<<1|1
    using namespace std;
    const int N=1000005;
    int n,m,flag[N],L[N],R[N],*Q[N],cnt,tot,val[N];
    ll A[N],B[N];
    struct node{
        ll sum,a,b,st,len;
    }t[N];
    
    int gi(){
        int a=0;char x=getchar();
        while(x<'0'||x>'9')x=getchar();
        while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+(x^48),x=getchar();
        return a;
    }
    
    il bool cmp(int *a,int *b){return *a<*b;}
    
    ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
    
    ll calc(ll a,ll b,ll n){
        ll x=a/b;a%=b;
        ll sum=n*(n+1)/2*x;
        if(!a||!b) return sum;
        ll lala=n/b,m=a*n/b;
        return sum+n*m-calc(b,a,m)+lala;
    }
    
    il ll solve(ll a,ll b,ll n){
        if(n<1)return 0;
        ll g=gcd(a,b);
        return n*(n+1)/2*a-b*calc(a/g,b/g,n);
    }
    
    il void pushup(int rt){t[rt].sum=t[rt<<1].sum+t[rt<<1|1].sum;}
    
    il void gai(ll A,ll B,ll st,int rt){
        t[rt].a=A,t[rt].b=B,t[rt].st=st;
        t[rt].sum=solve(A,B,st+t[rt].len-1)-solve(A,B,st-1);
    }    
    
    il void pushdown(int rt){
        if(t[rt].b){
            gai(t[rt].a,t[rt].b,t[rt].st,rt<<1);
            gai(t[rt].a,t[rt].b,t[rt].st+t[rt<<1].len,rt<<1|1);
            t[rt].b=0;
        }
    }
    
    void build(int l,int r,int rt){
        if(l+1==r){t[rt]=node{0,0,0,0,val[r]-val[l]};return;}
        int m=l+r>>1;
        t[rt]=node{0,0,0,0,val[r]-val[l]};
        build(lson),build(rson);
    }
    
    void update(ll A,ll B,int L,int R,int l,int r,int rt){
        if(R<=l||r<=L)return;
        if(L<=l&&R>=r){gai(A,B,val[l]-val[L]+1,rt);return;}
        pushdown(rt);
        int m=l+r>>1;
        if(L<=m) update(A,B,L,R,lson);
        if(R>=m) update(A,B,L,R,rson);
        pushup(rt);
    }
    
    ll query(int L,int R,int l,int r,int rt){
        if(R<=l||r<=L)return 0;
        if(L<=l&&R>=r)return t[rt].sum;
        pushdown(rt);
        int m=l+r>>1;
        ll ret=0;
        if(L<=m) ret+=query(L,R,lson);
        if(R>=m) ret+=query(L,R,rson);
        return ret;
    }
    
    int main(){
        n=gi(),m=gi();
        For(i,1,m) {
            flag[i]=gi(),L[i]=gi()-1,R[i]=gi(),Q[++tot]=&L[i],Q[++tot]=&R[i];
            if(flag[i]==1) A[i]=gi(),B[i]=gi();
        }
        sort(Q+1,Q+tot+1,cmp);
        int lst=-1;
        For(i,1,tot) if(*Q[i]!=lst) lst=*Q[i],*Q[i]=++cnt,val[cnt]=lst;else *Q[i]=cnt;
        build(1,cnt,1);
        For(i,1,m)
            if(flag[i]==1) update(A[i],B[i],L[i],R[i],1,cnt,1);
            else printf("%lld
    ",query(L[i],R[i],1,cnt,1));
        return 0;
    }    
  • 相关阅读:
    BZOJ.1016.[JSOI2008]最小生成树计数(Matrix Tree定理 Kruskal)
    BZOJ.4031.[HEOI2015]小Z的房间(Matrix Tree定理 辗转相除)
    BZOJ.1014.[JSOI2008]火星人(Splay 二分 Hash)
    BZOJ.4903.[CTSC2017]吉夫特(Lucas DP)
    BZOJ.1011.[HNOI2008]遥远的行星(思路 枚举)
    BZOJ.1013.[JSOI2008]球形空间产生器(高斯消元)
    BZOJ.1007.[HNOI2008]水平可见直线(凸壳 单调栈)
    BZOJ.1003.[ZJOI2006]物流运输(DP 最短路Dijkstra)
    BZOJ.1001.[BeiJing2006]狼抓兔子(最小割ISAP)
    BZOJ.1085.[SCOI2005]骑士精神(迭代加深搜索)
  • 原文地址:https://www.cnblogs.com/five20/p/9607384.html
Copyright © 2011-2022 走看看