类欧几里得算法 是 欧几里得算法 的拓展.
这里介绍万能欧几里得算法, 他适用性广泛, 实现简单, 相信你一下就能学会.
万能欧几里得算法的使用场景为:
在一个平面直角坐标系中, 有一条直线 y = p x + r q y=\dfrac {px+r}q y=qpx+r ,当其碰到一条横线时 执行 U U U 操作, 碰到一条竖线时执行 R R R 操作.(特别的, 当同时碰到定义为先执行 U U U).
操作必须满足结合律, 交换律不需要有.
我们定义两个操作 s , t s,t s,t 的结合为 s + t s+t s+t 或者 s ⋅ t s \cdot t s⋅t .
比如这条直线在 ( 0 , 5 ] (0,5] (0,5] 的定义域下, 执行的操作为 R R U R R U R RRU ~~RRUR RRU RRUR
设 s o l v e ( p , q , r , l , a , b ) solve(p, q, r,l, a, b) solve(p,q,r,l,a,b) 表示 y = p x + r q y = \dfrac{px + r}q y=qpx+r 在 ( 0 , l ] (0,l] (0,l] 内, U U U操作为 a a a, R R R 操作为 b b b 的总操作情况.
当 p ≥ q p \ge q p≥q, 一次 R R R 操作前面一定有 ⌊ p q ⌋ \lfloor \dfrac p q\rfloor ⌊qp⌋ 次 U U U 操作, 所以我们可以给他们绑定一下, 此时 r e t u r n s o l v e ( p m o d q , q , r , l , a , a ⌊ p q ⌋ b ) return ~solve(p \mod q,q,r,l,a, a^{\lfloor \frac p q\rfloor } b) return solve(pmodq,q,r,l,a,a⌊qp⌋b).
否则, 我们考虑给坐标轴来个对 $x = y $ 轴对称.
我们先来考虑一个简单的问题, 第 a a a 个 R R R 前面有多少个 U U U.
显然, 我们直接算他的 y y y 坐标即可, 所以有 ⌊ p a + r q ⌋ \lfloor \dfrac {pa + r}q\rfloor ⌊qpa+r⌋.
然后考量一下, 第 a a a 个 U U U 前面有多少个 R R R.
设第 b b b 个 R R R 在其前面, 则有
⌊ p b + r q ⌋ < a p b + r < a q p b ≤ a q − r − 1 b ≤ ⌊ a q − r − 1 p ⌋ \lfloor \dfrac {pb + r}q\rfloor < a\\ pb + r
所以有 ⌊ a q − r − 1 p ⌋ \lfloor \dfrac {aq-r-1}p\rfloor ⌊paq−r−1⌋ 这么多个.
现在问题就好解决了!!
大体上就是 重定义直线为 y = q x − r − 1 p y = \dfrac {qx-r-1}p y=pqx−r−1 , 然后每碰到一次 横线执行 R R R, 碰到竖线执行 U U U.
然后边界情况我们暴力处理:
系数自己推一下就行啦~~
node solve(ll p, ll q, ll r, ll l, node a, node b) {
if (!l) //定义域为空
return node();
if (p >= q)
return solve(p % q, q, r, l, a, power(a, p / q) + b);
ll m = (l * p + r) / q; //有多少个 U
if (!m) //只有 R操作了
return power(b, l);
return power(b, (q - r - 1) / p) + a + solve(q, p, (q - r - 1) % p, m - 1, b, a)
+ power(b, l - (q * m - r - 1) / p);
}
乍一看这个算法大概是 O ( log 2 max ( p , q ) ) O(\log ^2 \max (p,q)) O(log2max(p,q)) 的(当合并操作为 O ( 1 ) O(1) O(1) )
实际上不然.
可以发现每一层的复杂度为 log max ( p , q ) − log min ( p , q ) \log \max(p,q) - \log \min(p, q) logmax(p,q)−logmin(p,q) 所以总复杂度应该为 O ( log max ( p , q ) ) O(\log \max(p,q)) O(logmax(p,q)).
简单的模板题.(并没有用到万能欧几里得算法)
设 n = c / a n = c/a n=c/a, 则 a n s = ∑ x = 0 n ⌊ 1 + a x + c m o d a b ⌋ ans = \sum_{x=0}^n \lfloor 1+\dfrac {ax + c \mod a}b\rfloor ans=∑x=0n⌊1+bax+cmoda⌋
设 f ( p , q , r , n ) = ∑ x = 0 n ⌊ p x + r q ⌋ f(p,q,r,n)=\sum_{x=0}^n \lfloor \dfrac {px +r }q\rfloor f(p,q,r,n)=∑x=0n⌊qpx+r⌋.
如果 p ≥ q ∪ r ≥ q p\ge q \cup r\ge q p≥q∪r≥q, 我们可以先把部分确定的值( ⌊ r q ⌋ . . \lfloor \dfrac r q\rfloor.. ⌊qr⌋..)给提出来.
剩下情况皆为 p < q ∩ r < q p < q \cap r < q p<q∩r<q, 我们大力推式子:
{ f ( p , q , r , n ) = ∑ x = 0 n ⌊ p x + r q ⌋ = ∑ x = 0 n ∑ y = 0 y < ⌊ p x + r q ⌋ ( m = ( p n + r ) / q ) = ∑ y = 0 m − 1 n − ⌊ q y + q − r − 1 p ⌋ = n m − f ( q , p , q − r − 1 , m − 1 ) \begin{cases}f(p,q,r,n)&=\sum_{x=0}^n \lfloor \dfrac {px +r }q\rfloor\\ &=\sum_{x=0}^n \sum_{y=0}y < \lfloor \dfrac {px +r }q\rfloor \\ & (m = (pn+r)/q) \\ &= \sum_{y=0}^{m - 1} n- \lfloor \dfrac {qy + q - r - 1}p\rfloor\\ &= nm - f(q, p, q - r-1, m-1) \end{cases} ⎩ ⎨ ⎧f(p,q,r,n)=∑x=0n⌊qpx+r⌋=∑x=0n∑y=0y<⌊qpx+r⌋(m=(pn+r)/q)=∑y=0m−1n−⌊pqy+q−r−1⌋=nm−f(q,p,q−r−1,m−1)
ll f(ll p, ll q, ll r, ll n) {
if (!p)
return 0;
if (p >= q || r >= q)
return p / q * (n + 1) * n / 2 + r / q * (n + 1) + f(p % q, q, r % q, n);
ll m = (p * n + r) / q;
return n * m - f(q, p, q - r - 1, m - 1);
}
void solve() {
ll p, q, r, n;
qr(p, q, r);
n = r / p;
pr2(n + 1 + f(p, q, r % p, n));
}
这个我们维护一下 x , y , s u m x , s u m y , s q u a r e y , p r o d o f x , y x,y, sumx,sumy,squarey, prod~of ~x,y x,y,sumx,sumy,squarey,prod of x,y 即可.
TPP void ad(t1& x, t2 y) {
x += y;
if (x >= mod)
x -= mod;
}
TPP void dl(t1& x, t2 y) {
x -= y;
if (x < 0)
x += mod;
}
struct node {
ll x, y, sx, sy, ss, p;
node() { x = y = sx = sy = ss = p = 0; }
node operator+(node b) {
node c = *this;
ad(c.x, b.x);
ad(c.y, b.y);
ad(c.sx, b.sx);
ad(c.sx, x * b.x % mod);
ad(c.sy, b.sy);
ad(c.sy, y * b.x % mod);
ad(c.ss, (b.ss + y * y % mod * b.x + 2 * y * b.sy) % mod);
ad(c.p, (b.p + b.sx * y + b.sy * x + x * y % mod * b.x) % mod);
return c;
}
} U, R, ans;
node power(node a, int b) {
node c;
while (b) {
if (b & 1)
c = c + a;
b /= 2;
a = a + a;
}
return c;
}
node solve(ll p, ll q, ll r, ll l, node a, node b) {
if (!l)
return node();
if (p >= q)
return solve(p % q, q, r, l, a, power(a, p / q) + b);
ll m = (l * p + r) / q;
if (!m)
return power(b, l);
return power(b, (q - r - 1) / p) + a + solve(q, p, (q - r - 1) % p, m - 1, b, a)
+ power(b, l - (q * m - r - 1) / p);
}
void solve() {
ll l, p, q, r;
qr(l, p, r, q);
ans = power(U, r / q) + solve(p, q, r % q, l, U, R);
r /= q;
pr1((ans.sy + r) % mod, (ans.ss + r * r) % mod, ans.p);
puts("");
}
int main() {
U.y = 1;
R.x = R.sx = 1;
int T = 1;
qr(T);
while (T--)
solve();
return 0;
}
如果 R R R 为完全平方数就比较平凡.
我们着重讨论另一部分!
首先做一个转换:
( − 1 ) x = ⌊ x 2 ⌋ ∗ 4 − ⌊ x ⌋ ∗ 2 + 1 A n s = ∑ x = 1 n ( − 1 ) ⌊ d R ⌋ = ∑ x = 1 n ⌊ d R 2 ⌋ ∗ 4 − ⌊ d R ⌋ ∗ 2 + 1 (-1)^x = \lfloor \dfrac x 2\rfloor * 4 - \lfloor x \rfloor * 2 + 1 \\ Ans=\sum_{x=1}^n (-1)^{\lfloor d\sqrt R\rfloor} \\ =\sum_{x=1}^n \lfloor \dfrac {d\sqrt R} 2\rfloor * 4 - \lfloor d\sqrt R \rfloor * 2 + 1 (−1)x=⌊2x⌋∗4−⌊x⌋∗2+1Ans=x=1∑n(−1)⌊dR⌋=x=1∑n⌊2dR⌋∗4−⌊dR⌋∗2+1
所以, 现在的问题是求一个斜率带 R \sqrt R R 的直线下的整点数.
这个题比较卡精度, 如果直接用 l o n g d o u b l e long ~double long double 会错(然而并没有实现过, qaq, 这是听别人讲的
我们考虑把斜率扩一下域, 设 t = R , p t + r q t = \sqrt R, \dfrac {pt + r}q t=R,qpt+r 为斜率.
我们来考虑一个子问题:
f ( n , p , q , r ) = ∑ i = 1 n ⌊ p t + r q ⋅ i ⌋ f(n, p, q,r)=\sum_{i=1}^n \lfloor \dfrac {pt + r}q\cdot i \rfloor f(n,p,q,r)=i=1∑n⌊qpt+r⋅i⌋
我们可以先把 f l o o r ( p t + R q ) floor(\dfrac {pt + R}q) floor(qpt+R) 这一部分求答案, 然后 R R R 修改一下.
此时, 我们把坐标轴翻转一下, 由于斜率带 t t t 所以这条直线没有经过一个整点.(除了原点)
设 m m m 为定义域下最大的 y y y 坐标.
f ( n , p , q , r ) = ∑ i = 1 n ⌊ p t + r q ⋅ i ⌋ = = ∑ i = 1 m n − ⌊ q p t + r ⋅ i ⌋ = n m − ∑ i = 1 m ⌊ q p t + r ⋅ i ⌋ = n m − ∑ i = 1 m ⌊ q ∗ ( p t − r ) ( p t + r ) ∗ ( p t − r ) ⋅ i ⌋ = n m − ∑ i = 1 m ⌊ q p t − q r ) p 2 R − r 2 ⋅ i ⌋ = n m − f ( m , q p , p 2 R − r 2 , − q r ) f(n, p, q,r)=\sum_{i=1}^n \lfloor \dfrac {pt + r}q\cdot i \rfloor=\\ =\sum_{i=1}^m n - \lfloor \dfrac q{pt + r}\cdot i \rfloor \\ =nm - \sum_{i=1}^m \lfloor \dfrac q{pt + r}\cdot i \rfloor \\ =nm - \sum_{i=1}^m \lfloor \dfrac {q*(pt -r)}{(pt + r)*(pt -r)}\cdot i \rfloor \\ =nm - \sum_{i=1}^m \lfloor \dfrac {qpt -qr)}{p^2R - r^2}\cdot i \rfloor \\ =nm - f(m, qp,p^2R-r^2, -qr) f(n,p,q,r)=i=1∑n⌊qpt+r⋅i⌋==i=1∑mn−⌊pt+rq⋅i⌋=nm−i=1∑m⌊pt+rq⋅i⌋=nm−i=1∑m⌊(pt+r)∗(pt−r)q∗(pt−r)⋅i⌋=nm−i=1∑m⌊p2R−r2qpt−qr)⋅i⌋=nm−f(m,qp,p2R−r2,−qr)
这个递归的边界为 n = 0 n=0 n=0.
然后, 我们来考虑一下递归的层数如何, 因为这就是复杂度.
设 k k k 为斜率, 那么每次相当于 k % = 1 , n ∗ = k k\%=1,n*=k k%=1,n∗=k.
k ≤ 1 / 2 k\le 1/2 k≤1/2, 那么显然 m ≤ n / 2 m\le n/2 m≤n/2.
否则, 1 / k ∈ ( 1 , 2 ) 1/k \in (1,2) 1/k∈(1,2), 那么两次递归导致 n n n 的变化率为 k ( 1 / k − 1 ) = 1 − k < 1 / 2 k(1/k - 1)=1-k < 1/2 k(1/k−1)=1−k<1/2.
所以每减小一半需要 O ( 1 ) O(1) O(1) 次递归, 显然复杂度为 O ( log n ) O(\log n) O(logn).
TIP: 每次 p , q , r p,q,r p,q,r 要除一下 g c d gcd gcd 防止溢出. (至于为啥 l o n g l o n g long ~long long long 存得下,我不会分析, 有谁可以教教我吗/kel)
ll n, m;
double t;
ll f(ll n, ll p, ll q, ll r) { // ! sum i in [1, n] floor( (pt + r) / q * i )
if (!n)
return 0;
ll y, ans;
y = __gcd(p, __gcd(q, r));
p /= y;
q /= y;
r /= y;
y = (p * t + r) / q, ans = (n + 1) * n / 2 * y;
r -= q * y;
y = (p * t + r) * n / q;
return ans + n * y - f(y, p * q, p * p * m - r * r, -q * r);
}
void solve() {
qr(n, m);
t = sqrt(m);
if ((int)t * t == m)
pr2(m & 1 ? (n & 1 ? -1 : 0) : n);
else
pr2(n + 4 * f(n, 1, 2, 0) - 2 * f(n, 1, 1, 0));
}
这里套用万能欧几里得算法:
n o d e node node 中记录 x , y , A x , B y , p r o d A B x,y, A^x, B^y,prod_{AB} x,y,Ax,By,prodAB.
然后就比较平凡了.
TIP:用long double 防止溢出 l o n g l o n g long ~long long long.
ll p, q, r, l;
int n;
struct rec {
int a[N][N];
rec(int v = 0) {
memset(a, 0, sizeof a);
if (v)
FOR(i, n) a[i][i] = v;
}
rec operator*(const rec b) const {
rec c;
FOR(i, n) FOR(j, n) FOR(k, n) ad(c.a[i][k], 1LL * a[i][j] * b.a[j][k] % mod);
return c;
}
void operator+=(const rec b) { FOR(i, n) FOR(j, n) ad(a[i][j], b.a[i][j]); }
void init() { FOR(i, n) FOR(j, n) qr(a[i][j]); }
};
struct node {
ll x, y;
rec A, B, S;
node() {
x = y = 0;
A = B = rec(1);
S = rec();
}
node operator+(const node& b) {
node c = *this;
c.S += A * b.S * B;
c.A = c.A * b.A;
c.B = c.B * b.B;
ad(c.x, b.x);
ad(c.y, b.y);
return c;
}
node operator^(ll b) {
node c, a = *this;
while (b) {
if (b & 1)
c = c + a;
b /= 2;
a = a + a;
}
return c;
}
};
node f(ll p, ll q, ll r, ll n, node a, node b) {
if (!n)
return node();
if (p >= q)
return f(p % q, q, r, n, a, (a ^ p / q) + b);
ll m = ((long double)p * n + r) / q;
if (!m)
return b ^ n;
return (b ^ (q - r - 1) / p) + a + f(q, p, (q - r - 1) % p, m - 1, b, a)
+ (b ^ n - ((ll)(((long double)q * m - r - 1) / p)));
}
void solve() {
qr(p, q, r, l, n);
rec A, B;
node U, R, ans;
A.init();
B.init();
U.y = 1;
U.B = B;
R.x = 1;
R.A = R.S = A;
ans = (U ^ r / q) + f(p, q, r % q, l, U, R);
// int last = 0, v;
// FOR(i, l) {
// v = (p * i + r) / q;
// if (v ^ last)
// ans = ans + (U ^ v - last), last = v;
// ans = ans + R;
// FOR(x, n) {
// FOR(j, n) pr1(ans.S.a[x][j]);
// puts("");
// }
// }
FOR(i, n) {
FOR(j, n) pr1(ans.S.a[i][j]);
puts("");
}
}
又是板子qaq.
这个博客 写得挺好的.
前置知识: 拉格朗日插值.
#include
#define gc getchar()
#define mk make_pair
#define TP template <class o>
#define TPP template <typename t1, typename t2>
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define REP(i, a, b) for (int i = b; i >= a; i--)
#define FOR(i, n) rep(i, 1, n)
using namespace std;
typedef long long ll;
const int N = 13, mod = (int)1e9 + 7;
TP void qr(o& x) {
char c = gc;
x = 0;
int f = 1;
while (!isdigit(c)) {
if (c == '-')
f = -1;
c = gc;
}
while (isdigit(c))
x = x * 10 + c - '0', c = gc;
x *= f;
}
template <class o, class... O> void qr(o& x, O&... y) {
qr(x);
qr(y...);
}
TP void qw(o x) {
if (x / 10)
qw(x / 10);
putchar(x % 10 + '0');
}
TP void pr2(o x) {
if (x < 0)
x = -x, putchar('-');
qw(x);
putchar(10);
}
TPP void ad(t1& x, t2 y) {
x += y;
if (x >= mod)
x -= mod;
}
TPP void dl(t1& x, t2 y) {
x -= y;
if (x < 0)
x += mod;
}
struct arr {
ll a[N];
int n;
arr(int _n = 0)
: n(_n) {
memset(a, 0, sizeof a);
}
inline ll& operator[](int x) { return a[x]; }
void operator+=(arr b) { rep(i, 0, n) ad(a[i], b[i]); }
arr operator*(arr b) {
arr c;
c.n = n + b.n;
rep(i, 0, n) rep(j, 0, b.n) ad(c[i + j], a[i] * b[j] % mod);
return c;
}
ll operator()(ll x) {
ll ans = a[n];
REP(i, 0, n - 1) ans = (ans * x + a[i]) % mod;
return ans;
}
} A[N];
ll C[N][N], F[66][N][N];
ll power(ll a, ll b = mod - 2, ll p = mod) {
ll c = 1;
while (b) {
if (b & 1)
c = c * a % p;
b /= 2;
a = a * a % p;
}
return c;
}
void init() {
int n = 10;
rep(i, 0, n) {
C[i][0] = 1;
FOR(j, i) C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
rep(i, 0, n) {
ll s = 0;
arr x(i + 1), tmp(1);
rep(j, 0, i + 1) {
ad(s, power(j, i));
arr y;
y[0] = 1;
rep(k, 0, i + 1) if (j ^ k) y[0] = y[0] * (j - k) % mod;
y[0] = (power(y[0]) + mod) * s % mod;
rep(k, 0, i + 1) if (j ^ k) {
tmp[0] = mod - k;
tmp[1] = 1;
y = y * tmp;
}
x += y;
}
A[i] = x;
}
}
ll f(ll p, ll q, ll r, ll n, int k1, int k2, int d) {
ll& ans = F[d][k1][k2];
if (~ans)
return ans;
ll m = (p * n + r) / q;
if (!n)
return ans = k1 ? 0LL : power(m, k2);
ans = A[k1](n) * power(m, k2) % mod;
if (!p || !k2)
return ans;
if (p >= q || r >= q) {
ll u = p / q, v = r / q;
p %= q;
r %= q;
ans = 0;
for (int i = 0, I = 1; i <= k2 && I; i++, I = I * u % mod)
for (int j = 0, J = I; i + j <= k2 && J; j++, J = J * v % mod)
ad(ans, C[k2][i] * C[k2 - i][j] * f(p, q, r, n, k1 + i, k2 - i - j, d + 1) % mod * J % mod);
} else {
--m;
rep(k, 0, k2 - 1) rep(t, 0, k1 + 1)
dl(ans, C[k2][k] * A[k1].a[t] % mod * f(q, p, q - r - 1, m, k, t, d + 1) % mod);
}
return ans;
}
void solve() {
ll p, q, r, n, k1, k2;
qr(n, p, r, q, k1, k2);
memset(F, -1, sizeof F);
pr2(f(p, q, r, n, k1, k2, 0));
}
int main() {
init();
int T = 1;
qr(T);
while (T--)
solve();
return 0;
}
看到一条直线 + 下取整应该能直接想到用类欧.
然后, 维护好像也不是特别困难…
重要的是前面的大量转化…
对了, 好像有一个trick没讲:
对于 f ( 1 ) ⊕ f ( 2 ) ⊕ . . . . ⊕ f ( n ) f(1)\oplus f(2)\oplus....\oplus f(n) f(1)⊕f(2)⊕....⊕f(n) 的值.
我们考虑拆位, 然后求 m o d 2 \mod 2 mod2 下的总和即可.(大家应该都会吧)