阿凡达

题目大意

一个长度为n的序列,初始全0。有两种操作。
1、将[l,r]重新设置A mod B,2A mod B,3A mod B,4A mod B,5A mod B……(r-l+1)A mod B。
2、求[l,r]的和。

前置技能

我们如何求 ni=1iA mod B
就是求 ni=1iAiABB
An(n+1)2Bni=1iAB
考虑怎么求后面那个东西,设为solve(A,B,n)
ni=1iAB
如果A是B的倍数,我们很容易计算,设A=Bk,那么答案为 kn(n+1)2 ,可以直接计算出来。
否则,我们考虑A=Bk+a
ni=1i(Bk+a)B
kn(n+1)2+ni=1iaB
然后A用a代替。
这其实就是把A模了B,使得 A<B
ni=1iAB
ni=1nABj=1[iAB>=j]
nABj=1ni=1[iAB>=j]
nABj=1(nni=1[iAB<j])
nABj=1(nni=1[jBA>i])
nABj=1(nni=1jBA[A|jB])
注意到[A|jB]等价于[B|iA]
即设L=jB,那么B|L,现在假设A|L
因为jB<=nA,所以L<=nA,那么L可以用iA表示,又有B|L,所以等价。
nABn(n+1)2+ni=1[B|iA]nABj=1jBA
第二项可以计算A与B的lcm来得到最小满足条件的i,然后i的倍数一定都合法,否则一定不合法,贡献为 ni
最后一项就是 solve(B,A,jBA) ,递归计算即可
观察solve的前两项,每次就是从(A,B)变成了(B,A%B),所以这是一个类欧几里得算法,复杂度为log。

线段树

有了这个技能可以直接线段树维护标记。
标记形式就是A和B系数,以及从几开始。
只要实现了solve可以前缀和得到。
我不知道如何处理爆掉的情况……贴个除了爆了类型肯定正确的代码。。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxtot=1000000+10;
int left[maxtot],right[maxtot],a[maxtot],b[maxtot],low[maxtot],up[maxtot];
ll sum[maxtot];
int i,j,k,l,r,t,n,m,tot,root;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
ll lcm(int a,int b){
    return (ll)a*b/gcd(a,b);
}
ll solve(int A,int B,ll n){
    ll i,k,l=0;
    if (A%B==0){
        k=A/B;
        l=(ll)k*n*(n+1)/2;
        return l;
    }
    k=A/B;
    l=(ll)k*n*(n+1)/2;
    A%=B;
    l+=(ll)n*((ll)n*A/B);
    i=lcm(A,B)/A;
    l+=(ll)(n/i);
    l-=solve(B,A,(ll)n*A/B);
    return l;
}
ll work(int A,int B,int n){
    return (ll)A*n*(n+1)/2-(ll)B*solve(A,B,n);
}
void mark(int &x,int A,int B,int L,int R){
    if (!x) x=++tot;
    sum[x]=work(A,B,R)-work(A,B,L-1);
    a[x]=A;b[x]=B;low[x]=L;up[x]=R;
}
void down(int x,int l,int r){
    int mid=(l+r)/2;
    if (b[x]){
        mark(left[x],a[x],b[x],low[x],low[x]+mid-l);
        mark(right[x],a[x],b[x],low[x]+mid-l+1,up[x]);
        b[x]=0;
    }
}
void change(int &x,int l,int r,int a,int b,int A,int B,int L,int R){
    if (!x) x=++tot;
    if (l==a&&r==b){
        mark(x,A,B,L,R);
        return;
    }
    down(x,l,r);
    int mid=(l+r)/2;
    if (b<=mid) change(left[x],l,mid,a,b,A,B,L,R);
    else if (a>mid) change(right[x],mid+1,r,a,b,A,B,L,R);
    else{
        change(left[x],l,mid,a,mid,A,B,L,L+mid-a);
        change(right[x],mid+1,r,mid+1,b,A,B,L+mid-a+1,R);
    }
    sum[x]=sum[left[x]]+sum[right[x]];
}
ll query(int x,int l,int r,int a,int b){
    if (!x) return 0;
    if (l==a&&r==b) return sum[x];
    down(x,l,r);
    int mid=(l+r)/2;
    if (b<=mid) return query(left[x],l,mid,a,b);
    else if (a>mid) return query(right[x],mid+1,r,a,b);
    else return query(left[x],l,mid,a,mid)+query(right[x],mid+1,r,mid+1,b);
}
int main(){
    n=read();m=read();
    while (m--){
        t=read();
        if (t==1){
            l=read();r=read();j=read();k=read();
            change(root,1,n,l,r,j,k,1,r-l+1);
        }
        else{
            l=read();r=read();
            printf("%lld\n",query(root,1,n,l,r));
        }
    }
}

平衡树做法

待填

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值