【LuoguP4433】[COCI2009-2010#1] ALADIN(含类欧几里得算法推导)

题目链接

题意简述

区间赋值模意义下等差数列,询问区间和
\(N\leq 10^9,Q\leq 10^5\)

Sol

每次操作就是把操作区间\([L,R]\)中的数赋值成:
\[(X-L+1)*A\ mod\ B\]
考虑用线段树维护。
我们只需要能快速知道一段区间\([l,r]\)被覆盖后的和就行了,因为覆盖的标记易于下传:
\[\sum_{i=l}^{r} (i-L+1)*A\ mod\ B\]
根据基础的数学知识,mod显然不好算,把它拆开:
\[\sum_{i=l}^r (i-L+1)*A-\bigg\lfloor \frac{(i-L+1)*A}{B}\bigg\rfloor*B\]
前面那一坨直接算就行了,关键是后面这一坨。我们可以把后面的式子写简洁一些:
\[B*\sum_{i=q}^{p} \bigg\lfloor \frac{A*i}{B}\bigg\rfloor\]

其中\(p=l-L+1,q=r-l+1\),\(B\)乘在外面不用管,而\(p\)到\(q\)的求和可以拆分为\(0\sim q\) 的和减去\(0\sim p-1\)的和,这样我们只需要会算以下式子:
\[\sum_{i=0}^n \bigg\lfloor \frac{A*i}{B}\bigg\rfloor\]

这玩意就可以用类欧几里得算法求,具体过程如下。



第一种方法是用几何的思想直观的来理解,但是似乎不是很好推出明确的式子,所以这里主要用代数推导。
对于式子:
\[\sum_{i=0}^n \bigg\lfloor \frac{A*i+B}{C}\bigg\rfloor\]
其几何意义就是直线\(y=\dfrac{Ax+B}{C}\) 下方和x轴与y轴与直线\(x=n\)围成的图形中包含的纵坐标不为0的整点个数,这个画个图就很好理解:
\[令\ F(n,A,B,C)=\sum_{i=0}^n \bigg\lfloor \frac{A*i+B}{C}\bigg\rfloor\]
如果 \(A,B\)不小于 \(C\),那么可以把整除的部分提出来直接计算,这个很简单,所以只讨论\(A,B\)都小于\(C\)的情况。

记\(m=\big\lfloor \frac{A*n+B}{C}\big\rfloor\),通过几何意义转换(这里方便推式子j从0到m-1):
\[F(n,A,B,C)=\sum_{i=0}^n\sum_{j=0}^{m-1} \bigg[(A*i+B) \geq C*(j+1)\bigg]\]
调换求和顺序
\[F(n,A,B,C)=\sum_{i=0}^{m-1}\sum_{j=0}^n\bigg [(A*j+B)\geq C*(i+1)\bigg]\]

移项变形,减1去掉不等式的等号:
\[F(n,A,B,C)=\sum_{i=0}^{m-1}\sum_{j=0}^n \bigg[(C*i+C-B-1) < A*j\bigg]\]
接下来容斥一下,用总数减去不合法的情况:
\[F(n,A,B,C)=\sum_{i=0}^{m-1}(n+1-\sum_{j=0}^n \bigg[(C*i+C-B-1) \geq A*j\bigg])\]

这一下里面的不就和原来我们的\(F(x)\)的形式差不多了嘛。
考虑到我们之前\(j\)是从\(0\sim m-1\)而当\(j=0\)的时候,左边必大于0,而右边为0,贡献一定存在,故可以把原式改写,先把1的贡献减在外面:
\[F(n,A,B,C)=\sum_{i=0}^{m-1}(n-\sum_{j=0}^{n-1} \bigg[(C*i+C-B-1) \geq A*(j+1)\bigg])\]
这样不就提出\(n\)来:
\[F(n,A,B,C)=n*m-\sum_{i=0}^{m-1}\sum_{j=0}^{n-1} \bigg[(C*i+C-B-1) \geq A*(j+1)\bigg]\]
后面的式子很先然可以根据\(F(x)\)的定义改写:
\[F(n,A,B,C)=n*m-F(m-1,C,C-B-1,A)\]

这样递归下去不停计算,显然的是当最后\(A\)变成\(0\)的时候贡献为\(0\)
只用观察\(A\)和\(C\),发现他们调换了位置,这样必然使得下一次的\(C \leq A\),那么可以先把这部分提出来计算,实际上就是:\((A,C) \rightarrow (C\%A,A)\),这不是就是和\(gcd\)长的一样吗,所以复杂度也是\(O(logn)\)

其实类欧主要是一种把整除转化为求整点的思想

直接写类欧的代码差不多长这样:

ll likegcd(ll n,ll a,ll b,ll c){
    if(c<=a||c<=b) return (a/c*n%mod*(n+1)%mod*inv2%mod+b/c*(n+1)%mod+likegcd(n,a%c,b%c,c))%mod;
    if(!a||!n) return 0;
    ll m=(a*n+b)/c;
    return (n*m%mod-likegcd(m-1,c,c-b-1,a)+mod)%mod;
}


所以这道题也差不多做完了
但是有两个要注意的地方:
1.直接乘可能会爆\(long long\),所以\(\_\_int128\)大法好
2.本题卡空间所以要对操作区间和询问区间离散化,注意离散化的是\(l-1和r\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define Set(a,b) memset(a,b,sizeof(a))
template<class T>inline void init(T&x){
    x=0;char ch=getchar();bool t=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') t=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    if(t) x=-x;return;
}
int n,q;
//#define ll __int128
typedef long long ll;
const int MAXN=2e6+1;
const int M=5e4+1;
ll sum[MAXN];
int L[MAXN],R[MAXN];
int tagA[MAXN],tagB[MAXN],tagL[MAXN];
struct Query{
    int l,r,A,B;
}Q[M];
int stk[M<<1];int top=0;
#define ls (u<<1)
#define rs (u<<1|1)
void build(int u,int l,int r){
    if(l==r) {L[u]=stk[l-1]+1,R[u]=stk[r];return;}
    int mid=l+r>>1;
    build(ls,l,mid);build(rs,mid+1,r);
    L[u]=L[ls],R[u]=R[rs];return;
}
ll likegcd(ll n,ll a,ll b,ll c){
    if(c<=a||c<=b) return (a/c*(n*(n+1)/2)+b/c*(n+1)+likegcd(n,a%c,b%c,c));
    if(!a||!n) return 0;
    ll m=(a*n+b)/c;
    return n*m-likegcd(m-1,c,c-b-1,a);
}
inline void Cover(int u,int l,int r,int L,int A,int B){
    int len=r-l+1;
    sum[u]=1ll*(l+r-(L<<1)+2)*len/2*A-1ll*B*(likegcd(r-L+1,A,0,B)-likegcd(l-L,A,0,B));
    tagA[u]=A,tagB[u]=B;tagL[u]=L;
    return;
}
inline void push_down(int u){
    if(tagL[u]) {
        int lson=ls,rson=rs;
        Cover(lson,L[lson],R[lson],tagL[u],tagA[u],tagB[u]);
        Cover(rson,L[rson],R[rson],tagL[u],tagA[u],tagB[u]);
        tagL[u]=tagA[u]=tagB[u]=0;
    }
    return;
}
void Modify(int u,int l,int r,int NL,int NR,int LS,int A,int B){
    if(l>=NL&&r<=NR) return Cover(u,L[u],R[u],LS,A,B);
    int lson=ls,rson=rs;push_down(u);
    int mid=l+r>>1;
    if(mid>=NL) Modify(lson,l,mid,NL,NR,LS,A,B);
    if(mid< NR) Modify(rson,mid+1,r,NL,NR,LS,A,B);
    sum[u]=sum[lson]+sum[rson];
    return;
}
ll query(int u,int l,int r,int L,int R){
    if(l>=L&&r<=R) return sum[u];
    push_down(u);
    int mid=l+r>>1;
    if(mid>=R) return query(ls,l,mid,L,R);
    if(mid< L) return query(rs,mid+1,r,L,R);
    return query(ls,l,mid,L,mid)+query(rs,mid+1,r,mid+1,R);
}
int main()
{
    init(n);init(q);
    for(int i=1;i<=q;++i){//区间的离散化是把 l-1 和 r 离散
        int op;init(op);
        if(op==1){
            int l,r,A,B;
            init(l);init(r);init(A);init(B);
            Q[i].l=l,Q[i].r=r,Q[i].A=A,Q[i].B=B;
            stk[++top]=l-1,stk[++top]=r;
        }
        else {
            int l,r;init(l);init(r);
            Q[i].l=l,Q[i].r=r;
            stk[++top]=l-1,stk[++top]=r;
            Q[i].A=-1;
        }
    }
    stk[0]=0;
    sort(stk+1,stk+1+top);top=unique(stk+1,stk+1+top)-stk-1;
    build(1,1,top);
    for(int i=1;i<=q;++i){
        if(Q[i].A!=-1){
            int l=Q[i].l,r=Q[i].r,A=Q[i].A,B=Q[i].B;
            int Li=lower_bound(stk+1,stk+1+top,l-1)-stk+1;
            int Ri=lower_bound(stk+1,stk+1+top,r)-stk;
            Modify(1,1,top,Li,Ri,l,A,B);
        }
        else printf("%lld\n",query(1,1,top,lower_bound(stk+1,stk+1+top,Q[i].l-1)-stk+1,lower_bound(stk+1,stk+1+top,Q[i].r)-stk));
    }
}

原文地址:https://www.cnblogs.com/NeosKnight/p/10391312.html

时间: 2024-08-30 00:25:54

【LuoguP4433】[COCI2009-2010#1] ALADIN(含类欧几里得算法推导)的相关文章

扩展欧几里得算法的模板实现

我居然现在还记不住扩欧的板子,我太弱啦! 扩展欧几里得算法解决的是这样的问题: 给定一个不定方程组ax+by=gcd(a,b),求他的一组整数解 先给出实现代码 void exgcd(int a,int b,int &x,int &y) { if(!b) { x=1,y=0;//gcd(a,0)显然等于1*a-0*0=a return a; } int ans=exgcd(b,a%b,x,y); int tem=x; x=y; y-=tem-(a/b)*y; return ans;} 但实

欧几里得算法与扩展欧几里得算法_C++

先感谢参考文献:http://www.cnblogs.com/frog112111/archive/2012/08/19/2646012.html 注:以下讨论的数均为整数 一.欧几里得算法(重点是证明,对后续知识有用) 欧几里得算法,也叫辗转相除,简称 gcd,用于计算两个整数的最大公约数 定义 gcd(a,b) 为整数 a 与 b 的最大公约数 引理:gcd(a,b)=gcd(b,a%b) 证明: 设 r=a%b , c=gcd(a,b) 则 a=xc , b=yc , 其中x , y互质

数论及其应用——欧几里得算法

欧几里得是数论当中最基本的定理,以其为基础的拓展欧几里得算法在解决同余方程.求模逆元等问题. 首先来介绍几个概念,数论当中一些基本的概念其实在小学就学过,但是很长一段时间并没有用到它们,因此这里再拿出来温习一下. 我们常常用a|b来表示b能够整除a(b > a),即b/a是整数,但是“|”在使用的过程中容易和绝对值.几何定义符.条件概率混淆,所以,这里我们用a\b来表示a能够整除b. 约数:如果b\a,则称b是a的约数. 倍数:如果b\a,则称a是b的倍数. 最大公约数:gcd(a,b) = m

欧几里得算法

欧几里得算法 定义:欧几里得算法又叫做辗转相除法,用于计算两个整数的最大公约数. 首先,两个整数的最大公约数等于其中较小的那个数和两数的相除余数的最大公约数,证明如下: 假设两个整数a.b,其中a = kb + r,d为a.b任意公约数. 证明:因为d为a.b的公约数,所以a.b都可以被d整除,由a = kb + r可得,r = a - kb,则r/d = a/d - kb/d,因此r也可以被d整除.综上所述(a,b)的公约数和(b,r)相同.故最大公约数也是相同的. public static

证明欧几里得算法的正确性

欧几里得算法又叫辗转相除法,是求解最大公约数的一种古老的方法. 废话不多说,直接开证: 题目:求解正整数a,b(a >= b)的最大公约数. a总可以用b来表示:a = qb + p; 这个式子怎么理解呢? 我们可以这样理解:a是被除数,b是除数,q是商,p是余数(p = a % b). 设 r 为a,b的最大公约数. 则a,b能被r整除(废话- _ -). 下面重点来了:   上式成立. 又因为q*b/r为整除,a也为整数 所以p/r也为整数,即 p 能被 r 整除 此时 r 也是b, p的最

最大公约数-----欧几里得算法

欧几里得算法: 如果求两个数的最大公约数,那么最一般的求法是设置一个变量i=1,然后i不断加一,如果i加到某个数后两个数都能整除这个数了,然后把这个变量保存下来,然后最后的结果中最大的就是最大公约数. 然而这种方法时间复杂度可想而知有多高,所以一般情况瞎并不用这种方法,那么就有下面的欧几里得算法: 输入:两个数 a,b 输出:两个数的最大公约数 c 欧几里得算法:(1)找出两个数中最大的和最小的,分别为tmax.tmin, (2)不断令设置一个变量t代表tmin,tmin赋值为tmax  mod

欧几里得算法 - 计算两个正整数的最大公约数

欧几里得算法-计算两个正整数a,b的最大公约数 #定理:gcd(a,b) = gcd(b, a mod b) 终止条件:余数等于0 返回结果:余数等于0时的除数b # -*- coding: utf-8 -*- __author__ = 'nob' #迭代欧几里得 def iterative_gcd(a, b):     r = a % b     while(r):         a = b         b = r         r = a % b     return b     #

POJ - 1061 青蛙的约会 (扩展欧几里得算法)

Description 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止.可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置.不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的.但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的.为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面. 我们把这

[NOI2002] 荒岛野人 扩展欧几里得算法

[问题描述] 克里特岛以野人群居而著称.岛上有排列成环行的M个山洞.这些山洞顺时针编号为1,2,-,M.岛上住着N个野人,一开始依次住在山洞 C1,C2,-,CN中,以后每年,第i个野人会沿顺时针向前走Pi个洞住下来.每个野人i有一个寿命值Li,即生存的年数.下面四幅图描述了一个有6个 山洞,住有三个野人的岛上前四年的情况.三个野人初始的洞穴编号依次为1,2,3:每年要走过的洞穴数依次为3,7,2:寿命值依次为4,3,1.     奇怪的是,虽然野人有很多,但没有任何两个野人在有生之年处在同一个