类欧几里得算法
前言
注:该文章不定期更新。
Tips: 建议阅读文章后自行推导,否则难以掌握。
介绍
类欧几里得算法是用 \(O(\log n)\) 的时间复杂度求解形似于 \(f(a,b,c,n)=\sum\limits_{i=0}^n\lfloor\frac{ai+b}{c}\rfloor\) 的函数的值的一种算法。
由于其算法复杂度证明与扩展欧几里得算法类似,因此闻名于类欧几里得算法。
其主要思想就是划分不同边界推式子进行递归运算。
题单
P5170 【模板】类欧几里得算法
求下列函数的值。
思路
\(\text{(I)}\) 当 \(a < c\) 且 \(b < c\) 时。
先化简 \(f(a,b,c,n)\)。
令 \(m = \lfloor\frac{an+b}{c}\rfloor\),则有:
对于上述推导,我们称之为“引理1”:
引理1:
\[\sum_{i=0}^n\bigg\lfloor\frac{ai+b}{c}\bigg\rfloor=\sum_{j=0}^{\lfloor\frac{an+b}{c}\rfloor-1}\sum_{i=\lfloor\frac{cj+c-b - 1}{a}\rfloor+1}^n1 \]
接下来考虑 \(h(a,b,c,n)\) 的化简,根据“引理1”可得:
记 \(t = \lfloor\frac{cj+c-b-1}{a}\rfloor, m = \lfloor\frac{an+b}{c}\rfloor\),则有:
此时我们得到“引理2”:
引理2:
令 \(t = \lfloor\frac{cj+c-b-1}{a}\rfloor, m = \lfloor\frac{an+b}{c}\rfloor\),有:
\[\sum_{i=0}^ni\bigg\lfloor\frac{ai+b}{c}\bigg\rfloor = \sum_{j=0}^{m-1}\frac{(t+n+1)(n-t)}{2} \]
由于 \(t\) 的表达式中的 \(j\) 与求和相关,所以我们把 \(t\) 单独提出来。
于是可以化简原式。
然后考虑 \(g(a,b,c,n)\) 的化简,我们有一次求和公式 \(\sum\limits_{i=1}^n i = \frac{n^2+n}{2}\),可以反过来得到 \(2\sum\limits_{i=1}^n i - n = n^2\),于是我们的 \(g(a,b,c,n)\) 可以变成:
根据“引理2”可知,令 \(t = \lfloor\frac{cj+c-b-1}{a}\rfloor, m = \lfloor\frac{an+b}{c}\rfloor\),有:
由“引理1”可知:
由此,当 \(a \le c\) 且 \(b \le c\) 时,我们有:
\(\text{(II)}\) 当 \(a \ge c\) 或 \(b \ge c\) 时,我们将式子完全展开,令 \(a'=a \mod c\),\(b'=b \mod c\)。
根据公式写代码,为了防止已经算过的数据被重复计算,我们用结构体来存储三个函数同步计算,丑死了(小声)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353, inv2 = 499122177, inv6 = 166374059;
int T;
ll a, b, c, n;
struct Node
{
ll f, g, h;
};
Node solve(ll a, ll b, ll c, ll n)
{
Node ans, tmp;
if (!a) return (Node){(b / c) * (n + 1) % mod, (b / c) * (b / c) % mod * (n + 1) % mod, (b / c) * n % mod * (n + 1) % mod * inv2 % mod};
if (a < c && b < c)
{
ll m = (a * n + b) / c;
if (!m) return (Node){0, 0, 0};
tmp = solve(c, c - b - 1, a, m - 1);
m %= mod;
ans.f = (m * n % mod - tmp.f + mod) % mod;
ans.g = ((m * (m + 1) % mod * n % mod - 2 * tmp.h - 2 * tmp.f - ans.f) % mod + mod) % mod;
ans.h = ((m * n % mod * (n + 1) % mod - tmp.f - tmp.g) % mod + mod) % mod * inv2 % mod;
return ans;
}
tmp = solve(a % c, b % c, c, n);
ans.f = (tmp.f + n * (n + 1) % mod * inv2 % mod * (a / c) % mod + (n + 1) * (b / c) % mod) % mod;
ans.g = (tmp.g + (a / c) * (a / c) % mod * n % mod * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod + (n + 1) * (b / c) % mod * (b / c) % mod + 2 * (a / c) % mod * tmp.h % mod + 2 * (b / c) * tmp.f % mod + 2 * (a / c) * (b / c) % mod * n % mod * (n + 1) % mod * inv2 % mod) % mod;
ans.h = (tmp.h + (a / c) * n % mod * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod + (b / c) * n % mod * (n + 1) % mod * inv2 % mod) % mod;
return ans;
}
void solve()
{
scanf("%lld%lld%lld%lld", &n, &a, &b, &c);
Node ans = solve(a, b, c, n);
printf("%lld %lld %lld\n", ans.f, ans.g, ans.h);
}
int main()
{
scanf("%d", &T);
while (T -- ) solve();
return 0;
}
P5171 Earthquake
给定 \(a,\,b,\,c\) ,求满足方程 \(ax+by \leqslant c\) 的非负整数解个数。
思路
通过移项,我们可以得到:
由于 \(x,\,y\) 均为非负整数,我们可以令 \(x = y = 0\),则 \(x \leqslant \lfloor\frac{c}{a}\rfloor,\,y \leqslant \lfloor\frac{c}{b}\rfloor\)可以得到原方程的意义就是下面这个式子。
这个式子展开之后和我们刚才推的公式的形式很像,于是我们想办法展开。
为了让我们的公式形式和上面完全一致,需要把“\(-\)”改为“\(+\)”,于是我们可以在内函数加上一个 \(i\):
合并一下就是我们这题的公式:
为了保证满足 \(f(a,b,c,n) = \sum\limits_{i=0}^n\lfloor\frac{ai+b}{c}\rfloor\) 所有参数为非负的形式,我们需要让 \(b-a\geqslant0\),由于 \(a,\,b\) 等价,当 \(a>b\) 时直接 swap(a,b)
即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a, b, c;
ll solve(ll a, ll b, ll c, ll n)
{
if (!a) return b / c * (n + 1);
if (a < c && b < c)
{
ll m = (a * n + b) / c;
if (!m) return 0;
return n * m - solve(c, c - b - 1, a, m - 1);
}
return solve(a % c, b % c, c, n) + (n + 1) * n / 2 * (a / c) + (n + 1) * (b / c);
}
int main()
{
cin >> a >> b >> c;
if (a > b) swap(a, b);
cout << solve(b - a, c, b, c / a) - (c / a) * (c / a - 1) / 2 + 1;
return 0;
}
[ABC372G] Ax + By < C
与 P5171 Earthquake 完全类似。
不过上题是给定 \(a,\,b,\,c\) ,求满足方程 \(ax+by \leqslant c\) 的非负整数解个数。
本题是给定 \(a,\,b,\,c\) ,求满足方程 \(ax+by<c\) 的正整数解个数。
思路
同样的移项后可以得到:
而 \(x,y\) 有限制为 \(0<x\leqslant\lfloor\frac{c-b-1}{a}\rfloor,\,0<y\leqslant\lfloor\frac{c-a-1}{b}\rfloor\)
为了使 \(i\) 前的系数大于零,我们不妨考虑整体 \(+i-i\)。
对于 \(\sum\limits_{i=1}^{\lfloor\frac{c-b-1}{a}\rfloor}\lfloor\frac{(b-a)i+c-1}{b}\rfloor\) 可以类欧快速求解 \(f(b-a,\,c-1,\,b,\,\lfloor\frac{c-b-1}{a}\rfloor)-f(b-a,\,c-1,\,b,\,0)\),为了让 \(b - a\) 不为负,考虑 \(b - a < 0\) 时 swap(a, b)
即可。
以上是对于唯一 \(a,\,b,\,c\) 的单次求解,那么多元呢?
我们不妨考虑对于每条直线 \(A_ix+B_iy=C_i\) 本质上对空间进行了一次切割,我们想要找到的是所有直线在原点一侧的半平面交。
引用 ATCoder 上的一张图片,我们实际上想要找到的是左下角的点集,如果我们按照斜率排序,可以找到相邻两条直线的交点 \([x1,\,x2,\,...,\,x_k]\),我们对于 \([x_j,\,x_{j+1})\) 必定属于一条直线 \(l\),若 \(a,\,b,\,c\) 为直线 \(l\) 的三个参数,记 \(g(x) = \sum\limits_{i=1}^{x}\lfloor\frac{-ai+c-1}{b}\rfloor\),那么对于 \([x_j, x_{j+1})\) 区间上直线 \(l\) 下正整数点的个数为 \(g(x_{j+1}-1) - g(x_j-1)\)。
这样我们的问题就转变为了找到这样 \(k\) 个点。
首先我们的点的横坐标有上下界 \([1,\min\{\lceil\frac{c_1}{a_1}\rceil,\,...,\,\lceil\frac{c_i}{a_i}\rceil,\,...,\,\lceil\frac{c_n}{a_n}\rceil\}]\)。
维护一个单调栈使得栈内点横坐标单调递增,如果存在两条相邻斜率的直线交点的横坐标较小,那必然可以把大的舍去,因为不需要进行计算了。
这样这个问题就变成了对于交点集 \([x1,\,x2,\,...,\,x_k]\),求所有 \([x_j, x_{j+1})\) 区间上直线 \(l\) 下正整数点的个数 \(g(x_{j+1}-1) - g(x_j-1)\) 之和。
不过需要注意的是,我们的类欧的参数需要为正,此时的 \(a,\,b\) 就不等价了,这如何做到呢?
事实上有:
这样的 \(k\) 是很好找的,只需要 \(k = \lceil\frac{a}{b}\rceil\) 即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
const ll INF = 1e18;
struct Node
{
ll a, b, c;
}line[N];
ll x[N], q[N];
Node stk[N];
bool cmp(Node x, Node y)
{
if (x.a * y.b == y.a * x.b) return x.c * y.a < y.c * x.a;
return x.a * y.b < y.a * x.b;
}
ll calc(ll a, ll b, ll c, ll n)
{
if (n < 0) return 0;
if (!a) return b / c * (n + 1);
if (a < c && b < c)
{
ll m = (a * n + b) / c;
if (!m) return 0;
return n * m - calc(c, c - b - 1, a, m - 1);
}
return calc(a % c, b % c, c, n) + (n + 1) * n / 2 * (a / c) + (n + 1) * (b / c);
}
ll get(Node line, ll l, ll r, ll xmin)
{
l = min(xmin, max(1ll, l)) - 1, r = min(xmin, max(1ll, r)) - 1;
ll a = line.a, b = line.b, c = line.c;
ll k = (a + b - 1) / b;
return calc(k * b - a, c - 1, b, r) - k * r * (r + 1) / 2 - calc(k * b - a, c - 1, b, l) + k * l * (l + 1) / 2;
}
ll inter(Node l1, Node l2)
{
return (l2.c * l1.b - l1.c * l2.b - 1) / (l1.b * l2.a - l2.b * l1.a) + 1;
}
void solve()
{
ll n, cntx = 0, cnt = 0, ans = 0, xmin = 1e18;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> line[i].a >> line[i].b >> line[i].c;
sort(line + 1, line + n + 1, cmp);
for (int i = 1; i <= n; i ++ )
{
ll a = line[i].a, b = line[i].b, c = line[i].c;
if (cnt && stk[cnt].a * b == stk[cnt].b * a) continue;
while (cnt >= 2 && inter(stk[cnt], line[i]) <= x[cntx]) cnt -- , cntx -- ;
xmin = min((c + a - 1) / a, xmin);
stk[ ++ cnt] = line[i];
if (!cntx) x[ ++ cntx] = -INF;
else x[ ++ cntx] = inter(stk[cnt - 1], line[i]);
}
x[ ++ cntx] = INF;
for (int i = 1; i <= cnt; i ++ ) ans += get(stk[i], x[i], x[i + 1], xmin);
cout << ans << endl;
}
int main()
{
int T;
cin >> T;
while (T -- ) solve();
return 0;
}
T387782 开根
求下列式子的值。
思路
如果令 \(\sqrt{\theta}=x\),那么我们需要求出 \(\sum\limits_{i=0}^ni\lfloor{i}(x+\theta)\rfloor\) 的值,由于 \(i=0\) 时不影响函数的求和,我们求出 \(\sum\limits_{i=1}^ni\lfloor{i}(x+\theta)\rfloor\) 即可,考虑构造下列三个函数。
为了让函数进行递归,我们从最简单的函数开始考虑化简。
令 \(t = \frac{ax+b}{c}\),当 \(t \geqslant 1\) 时。
类似的,我们可以得到:
其中 \(h\) 函数只需要把平方暴力拆开即可。
然后考虑 \(t < 1\) 时,令 \(t = \frac{ax+b}{c},\,m=\lfloor tn\rfloor\)。
类似的,化简 \(g\) 和 \(h\),根据 \(f\) 化简的结论,我们快速写出式子。
设 \(m_j=\lfloor\frac{cj}{ax+b}\rfloor\),由等差数列求和可得:
相关项分离可得:
化简 \(h\) 时我们需要知道 \(n^2 = 2\sum\limits_{i=1}^ni-n\),这样便能愉快地化简了。
我们的答案即是 \(g(1,\theta,1,n)\)。
递归过程中可能出现许多被重复计算的值,所以我们用结构体存储 \(f,\,g,\,h\),同样,为了防止求解 \(t\) 时分子分母过大,我们每次运算前都除以其最大公约数。
时间复杂度 \(O(T\log{n})\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const ll mod = 998244353, inv2 = 499122177, inv6 = 166374059;
ll T, n, theta;
ld x;
struct Node
{
ll f, g, h;
};
Node solve(ll a, ll b, ll c, ll n)
{
if (!n) return Node{0, 0, 0};
ll d = __gcd(a, __gcd(b, c));
a /= d, b /= d, c /= d;
Node tmp, ans;
ll t = (a * x + b) / c;
if (!t)
{
ll m = (a * x + b) / c * n;
tmp = solve(a * c, -b * c, a * a * theta - b * b, m);
ans.f = ((n * m - tmp.f) % mod + mod) % mod;
ans.g = ((n * (n + 1) % mod * m % mod - tmp.f - tmp.h) % mod + mod) % mod * inv2 % mod;
ans.h = ((m * (m + 1) % mod * n % mod - 2 * tmp.g - 2 * ans.f) % mod + mod) % mod * inv2 % mod;
return ans;
}
tmp = solve(a, b - c * t, c, n);
ans.f = (n * (n + 1) % mod * t % mod * inv2 + tmp.f) % mod;
ans.g = (n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod * t % mod + tmp.g) % mod;
ans.h = (n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod * t % mod * t % mod + t * tmp.g % mod + tmp.h) % mod;
return ans;
}
int main()
{
scanf("%lld", &T);
while (T -- )
{
scanf("%lld%lld", &n, &theta);
x = sqrt(theta);
ll s = x;
if (!n || !theta) puts("0");
else if (n == 1) printf("%lld\n", s + theta);
else if (s * s == theta) printf("%lld\n", (n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod * s % mod + n * (n + 1) % mod * inv2 % mod * theta % mod) % mod);
else printf("%lld\n", solve(1, theta, 1, n).g);
}
return 0;
}
[2014清华集训] Sum
给定正整数 \(n,\,r\) ,求:
思路
首先这个式子看起来就不是很好递归的样子,我们把 \(-1\) 单独拿出来讨论,其 \(n\) 次幂必定满足:
我们把这个东西放进我们的式子。
再回到原式,我们就可以发现:
发现了什么?这不是和我们刚刚推得式子一模一样吗?
展开后得到:
刚刚我们推得式子是:
当 \(x=\sqrt{r}\) 时,分别令 \(a=1,\,b=0,\,c=1\) 和 \(a=1,\,b=0,\,c=2\),那么答案就显而易见了。
飞速的改一下版子(雾)。
#include<bits/stdc++.h>
using namespace std;
typedef long double ld;
typedef long long ll;
double x;
ll T, n, r;
ll solve(ll a, ll b, ll c, ll n)
{
if (!n) return 0;
ll d = __gcd(a, __gcd(b, c));
a /= d, b /= d, c /= d;
ll t = (a * x + b) / c;
if (!t)
{
ll m = (a * x + b) * n / c;
return n * m - solve(a * c, -b * c, a * a * r - b * b, m);
}
return n * (n + 1) / 2 * t + solve(a, b - c * t, c, n);
}
int main()
{
cin >> T;
while (T -- )
{
cin >> n >> r;
x = sqrt(r);
ll t = x;
if (t * t == r) cout << ((t & 1) ? -(n & 1) : n) << endl;
else cout << n - 2 * solve(1, 0, 1, n) + 4 * solve(1, 0, 2, n) << endl;
}
return 0;
}
[ABC283Ex] Popcount Sum
记 \(\text{popcount}(n)\) 为 \(n\) 的二进制表示中 \(1\) 的个数。
现在有 \(T\) 组询问,每组询问给定 \(n, m, r\),请求出
思路
遇到这种同余类问题显然可以换为关于 \(k\) 的一元线性函数,也就是说我们可以用类欧去解决这种问题,为了更好地拟合我们上面的式子,我们把 \(i\) 换成 \(k\),即 \(k \mod m = r\),简单划一下式子。
原式可化为:
拆位,每一位上是否含 \(1\) 我们可以这么表示。
设 \(P\) 为一正整数,\(P\) 的二进制位第 \(i\) 位上的数字应该为:
那么实际上就是这个东西。
从 \(j = 0\) 开始就是这么一个式子。
很好,带回原式可以得到:
由于数的二进制位是有限的,且不存在的位数一定都是 \(0\),所以我们可以把 \(\log_2{(im+r)}\) 改写为值域最高位,也就是 \(\log_2{n}\),交换求和顺序,就是枚举每个 \(j\),对 \(i\) 的函数进行类欧。
考虑发现也就是说求这个东西:
上板子!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll calc(ll a, ll b, ll c, ll n)
{
if (!a) return b / c * (n + 1);
if (a < c && b < c)
{
ll m = (a * n + b) / c;
if (!m) return 0;
return n * m - calc(c, c - b - 1, a, m - 1);
}
return calc(a % c, b % c, c, n) + (n + 1) * n / 2 * (a / c) + (n + 1) * (b / c);
}
void solve()
{
ll n, m, r, ans = 0;
scanf("%lld%lld%lld", &n, &m, &r);
int t = __lg(n);
for (int i = 0; i <= t; i ++ ) ans += calc(m, r, 1 << i, (n - r) / m) - 2 * calc(m, r, 1 << i + 1, (n - r) / m);
printf("%lld\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while (T -- ) solve();
return 0;
}
[COCI2009-2010#1] ALADIN
P4433 [COCI2009-2010#1] ALADIN
给你 \(n(1 \le n \le 10^9)\) 个盒子,有 \(q(1 \le q \le 5 \times 10^4)\) 个操作,操作有两种:
- 第一种操作输入格式为
1 L R A B
,表示将编号为 \(L\) 到 \(R\) 的盒子里的石头数量变为 \((X-L+1) \times A \bmod B\),其中 \(X\) 为盒子的编号。 - 第二种操作输入格式为
2 L R
,表示查询编号为 \(L\) 到 \(R\) 的盒子里的石头总数。
吐槽
一个极其恶心的类欧题,容易因为数据结构不熟练导致题目出错却找不到原因。
建立线段树变量多了还 MLE,浪费了我 2 天时间写一个题。
思路
首先注意到区间范围在 \(10^9\) 如此庞大的数量级上,我们知道,只能通过离散化之后再建立线段树才不会超出空间。
然后我们看向式子,是一个带模数的区间赋值,由 \(a \bmod b = a - b\lfloor\frac{a}{b}\rfloor\) 这个性质,我们考虑下面这个式子。
展开可以得到:
对于这个式子最复杂的成分 \(\sum\limits_{i=0}^{n-1}\lfloor\frac{ai+b}{c}\rfloor\) 也就是一阶类欧的求和 \(f(a,\, b,\, c,\, n-1)\)。
对于区间赋值 \([L, R]\),树上节点为 \([l, r]\),若满足 \(L \le l \le r \le R\),则该区间赋值有:
改一下形式也就是:
此式子满足上述推导过程得到的结论,令 \(a = A,\, b = (l - L + 1) \times A,\, c = B,\, n = r - l + 1\),可以发现,对于一个子段的赋值也就是 \(S(a,\,b,\,c,\,n-1) = \frac{an(n-1)}{2}+bn-cf(a,\,b,\,c,\,n-1)\)。
接下来考虑懒标记下传,为了维护式子的统一性,我们在每个节点内存储左端点 \(l\),右端点 \(r\),以及 \(a,\,b,\,c\) 的值,我们用大写字母来表示父亲的数据,\(T\) 表示父亲,\(tl,\, tr\) 分别表示左右儿子。
考虑 \(S(a,\,b,\,c,\,n-1)\) 的形式,由于 \(L\) 的不变性,\(a,\,b,\,c\) 显然为定值,唯一改变的就是 \(n - 1\) 的部分,而对于每个 \(n - 1\) 唯一对应线段树上一个节点所表示的区间范围 \([l,\, r]\),因此左儿子的转移一定。
考虑右儿子同样发现只有 \(l\) 发生了改变,我们直接的将求和式中的 \(i\) 的下限从 \(0\) 改为 \(r_{tl}-l_{tl}+1\) 其实也就是 \(l_{tr}-L\),简单推一下式子。
对于父亲有:
对于右儿子有:
可以发现 \(a = A,\, b = (l_{tr} - L)A + B,\, c = C,\, n = r_{tr} - l_{tl} + 1\),对于常数 \(b\) 我们可以直接对 \(c\) 取个模防止暴毙。
代码如下
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e4 + 10;
int n, m, q[N][5], root, idx;
vector<int> v;
struct Node
{
int l, r, ls, rs, a, b, c;
ll sum;
}tr[N << 2];
ll calc(ll a, ll b, ll c, ll n)
{
if (!a) return b / c * (n + 1);
if (a < c && b < c)
{
ll m = (a * n + b) / c;
if (!m) return 0;
return n * m - calc(c, c - b - 1, a, m - 1);
}
return calc(a % c, b % c, c, n) + (n + 1) * n / 2 * (a / c) + (n + 1) * (b / c);
}
ll solve(ll a, ll b, ll c, ll n)
{
return n * (n - 1) / 2 * a - c * calc(a, b, c, n - 1) + b * n;
}
void pushup(int p)
{
tr[p].sum = tr[tr[p].ls].sum + tr[tr[p].rs].sum;
}
void pushdown(int p)
{
if (tr[p].c)
{
tr[tr[p].ls].a = tr[p].a, tr[tr[p].ls].b = tr[p].b, tr[tr[p].ls].c = tr[p].c;
tr[tr[p].rs].a = tr[p].a, tr[tr[p].rs].b = (1ll * tr[p].a * (tr[tr[p].rs].l - tr[p].l) + tr[p].b) % tr[p].c, tr[tr[p].rs].c = tr[p].c;
tr[tr[p].ls].sum = solve(tr[tr[p].ls].a, tr[tr[p].ls].b, tr[tr[p].ls].c, tr[tr[p].ls].r - tr[tr[p].ls].l + 1);
tr[tr[p].rs].sum = solve(tr[tr[p].rs].a, tr[tr[p].rs].b, tr[tr[p].rs].c, tr[tr[p].rs].r - tr[tr[p].rs].l + 1);
tr[p].c = 0;
}
}
int build(int l, int r)
{
int p = ++ idx;
if (l == r)
{
tr[p].l = v[l];
tr[p].r = v[l + 1] - 1;
return p;
}
int mid = l + r >> 1;
tr[p].ls = build(l, mid), tr[p].rs = build(mid + 1, r);
tr[p].l = tr[tr[p].ls].l, tr[p].r = tr[tr[p].rs].r;
return p;
}
void modify(int p, int l, int r, int a, int c)
{
if (tr[p].l > r || tr[p].r < l) return;
if (tr[p].l >= l && tr[p].r <= r)
{
tr[p].a = a;
tr[p].b = (tr[p].l - l + 1ll) * a % c;
tr[p].c = c;
tr[p].sum = solve(tr[p].a, tr[p].b, tr[p].c, tr[p].r - tr[p].l + 1);
return;
}
pushdown(p);
modify(tr[p].ls, l, r, a, c), modify(tr[p].rs, l, r, a, c);
pushup(p);
}
ll query(int p, int l, int r)
{
if (tr[p].l > r || tr[p].r < l) return 0;
if (tr[p].l >= l && tr[p].r <= r) return tr[p].sum;
pushdown(p);
return query(tr[p].ls, l, r) + query(tr[p].rs, l, r);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ )
{
int op, l, r;
scanf("%d%d%d", &op, &l, &r);
q[i][0] = op, q[i][1] = l, q[i][2] = r;
v.push_back(l), v.push_back(r + 1);
if (op == 1)
{
int a, b;
scanf("%d%d", &a, &b);
q[i][3] = a, q[i][4] = b;
}
}
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
root = build(0, v.size() - 2);
for (int i = 1; i <= m; i ++ )
if (q[i][0] == 1) modify(root, q[i][1], q[i][2], q[i][3], q[i][4]);
else printf("%lld\n", query(root, q[i][1], q[i][2]));
return 0;
}