Hello everyone!↵
↵
In CP, linear recurrences with **constant coefficients** are a standard topic. Such problems are typically solved by building a transition matrix and using binary exponentiation to find the $N$-th term in $O(\log N)$ time (or by applying the Bostan-Mori algorithm for larger orders).↵
↵
However, the approach must change when the coefficients are **not** constant and instead depend on $n$.↵
↵
## What is a holonomic sequence?↵
↵
A sequence $a_n$ is called **holonomic** (or **P-recursive**) if it satisfies a linear recurrence relation where the coefficients are *polynomials* in $n$.↵
↵
Formally, there exist polynomials $P_0(x), P_1(x), \dots, P_k(x)$ (where $P_k \neq 0$) such that for all $n$:↵
↵
$$ P_k(n) a_{n+k} + P_{k-1}(n) a_{n+k-1} + \dots + P_0(n) a_n = 0 $$↵
↵
The integer $k$ is the **order** of the recurrence, and the maximum degree among the polynomials $P_i$ is the **degree** of the recurrence.↵
↵
**Classic Examples:**↵
↵
- **Factorials**: $1 \cdot a_n - n \cdot a_{n-1} = 0$. (Order 1, Degree 1).↵
- **Catalan numbers**: $(n+2) \cdot C_{n+1} - (4n+2) \cdot C_n = 0$. (Order 1, Degree 1).↵
- **Derangements**: $1 \cdot D_n - (n-1) \cdot D_{n-1} - (n-1) \cdot D_{n-2} = 0$. (Order 2, Degree 1).↵
↵
## Applications in CP↵
↵
A key property of holonomic sequences is their **closure** under various operations. ↵
If sequences $A$ and $B$ are holonomic, then the following sequences are *also* holonomic:↵
↵
1. $A + B$↵
2. $A \cdot B$↵
3. Prefix sums: $\sum_{i=0}^n A_i$↵
↵
This implies that for complex sums such as $\sum_{k=0}^n \binom{n}{k}^2 \binom{n+k}{k}^2$ (the Apery numbers), a polynomial recurrence is mathematically guaranteed to exist, as binomial coefficients and factorials are themselves holonomic.↵
↵
Analytically, these recurrences can be found using **Zeilberger's Algorithm** (Creative Telescoping), which algebraically simplifies the summation. ↵
↵
However, implementing Zeilberger's algorithm during a contest is highly impractical. Since it is theoretically guaranteed that a recurrence exists, one can bypass the analytical derivation and instead **guess the recurrence** programmatically.↵
↵
## Automated Guessing with Gaussian Elimination↵
↵
Consider a scenario where the $N$-th term of a sequence must be found in $O(N)$ time (for $N \le 10^7$), but the available direct calculation or dynamic programming approach takes $O(N^2)$. Linear algebra can be used to automatically deduce the underlying recurrence.↵
↵
Assume the recurrence has a small order $R$ (e.g., $R=2$) and polynomial degree $D$ (e.g., $D=1$). The equation takes the following form:↵
↵
$$ (c_{2,1} n + c_{2,0}) a_n + (c_{1,1} n + c_{1,0}) a_{n-1} + (c_{0,1} n + c_{0,0}) a_{n-2} = 0 $$↵
↵
Given that the values of $a_n, a_{n-1}, a_{n-2}$, and $n$ are known, the only unknowns are the constants $c_{i,j}$.↵
There are exactly $U = (R+1) \times (D+1)$ unknown constants. In this example, $U = 3 \times 2 = 6$.↵
↵
## Reference Implementation (C++)↵
↵
Below is a reference **solution for the problem 506E (Mr. Kitayuta's Gift)** utilizing this algorithm.↵
↵
**Problem 506E:** *Given a string $S$ ($|S| \le 200$), calculate the number of valid ways to insert exactly $N$ characters ($N \le 10^9$) such that the resulting string is a palindrome. All calculations should be modulo $10007$.*↵
↵
**How my solution works:**↵
↵
1. **Naive DP:** We write a simple $O(|S|^2 \cdot K)$ DP to compute the answers for small lengths.↵
2. **Parity Split (Crucial):** Even and odd length palindromes behave like two independent sequences. Mixing them inflates the recurrence order to $\sim 600$, causing a Time Limit Exceeded (TLE) during matrix exponentiation. By exclusively computing terms for the target parity $(|S| + N) \pmod 2$, we halve the recurrence order to $\le 305$.↵
3. **Automated Guessing:** We generate $K = 800$ terms and feed them into the Gaussian elimination algorithm with polynomial degree $D=0$ (which effectively finds a constant-coefficient recurrence).↵
4. **Optimized Matrix Exponentiation:** We construct the transition matrix and raise it to the power of $N/2$. Since $MOD = 10007$, we can safely omit the modulo operation in the innermost loop of the matrix multiplication, allowing the solution to comfortably run in $\sim 0.3s$.↵
↵
***↵
↵
<spoiler summary="Code for 506E">↵
↵
~~~~~↵
#include <bits/stdc++.h>↵
using namespace std;↵
↵
using ll = long long;↵
using ull = unsigned long long;↵
using ld = long double;↵
using pii = pair<int, int>;↵
using pll = pair<ll, ll>;↵
using vi = vector<int>;↵
using vl = vector<ll>;↵
using vvi = vector<vector<int>>;↵
using vvl = vector<vector<ll>>;↵
↵
#define all(x) (x).begin(), (x).end()↵
#define rall(x) (x).rbegin(), (x).rend()↵
#define sz(x) (int)(x).size()↵
#define pb push_back↵
#define mp make_pair↵
#define fi first↵
#define se second↵
#define FOR(i, a, b) for (int i = (a); i < (b); ++i)↵
#define RFOR(i, a, b) for (int i = (b)-1; i >= (a); --i)↵
#define each(x, a) for (auto &x : a)↵
↵
const int MOD = 10007;↵
const int INF = 1e9;↵
const ll LINF = 1e18;↵
↵
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }↵
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }↵
↵
ll bp(ll a, ll b, ll mod = MOD) {↵
ll r = 1;↵
a %= mod;↵
while (b > 0) {↵
if (b % 2 == 1) r = (r * a) % mod;↵
a = (a * a) % mod;↵
b /= 2;↵
}↵
return r;↵
}↵
↵
ll ext_inv(ll a, ll m) {↵
ll m0 = m, y = 0, x = 1;↵
if (m == 1) return 0;↵
while (a > 1) {↵
ll q = a / m;↵
ll t = m;↵
m = a % m, a = t;↵
t = y;↵
y = x - q * y;↵
x = t;↵
}↵
if (x < 0) x += m0;↵
return x;↵
}↵
↵
vvl guess(const vl& a, int R, int D) {↵
int u = (R + 1) * (D + 1);↵
int eq = sz(a) - R;↵
if (eq < u) return {};↵
vvi mt(eq, vi(u, 0));↵
FOR(i, 0, eq) {↵
int n = i + R;↵
int c = 0;↵
FOR(o, 0, R + 1) {↵
ll p = 1;↵
FOR(d, 0, D + 1) {↵
mt[i][c] = (p * a[n - o]) % MOD;↵
c++;↵
p = (p * n) % MOD;↵
}↵
}↵
}↵
int r = 0;↵
vi piv;↵
FOR(c, 0, u) {↵
if (r >= eq) break;↵
int p = r;↵
while (p < eq && mt[p][c] == 0) p++;↵
if (p == eq) continue;↵
swap(mt[r], mt[p]);↵
ll iv = ext_inv(mt[r][c], MOD);↵
FOR(j, c, u) mt[r][j] = (mt[r][j] * iv) % MOD;↵
FOR(k, 0, eq) {↵
if (k == r || mt[k][c] == 0) continue;↵
int f = mt[k][c];↵
for (int j = c; j < u; ++j) {↵
mt[k][j] = (mt[k][j] - f * mt[r][j]) % MOD;↵
if (mt[k][j] < 0) mt[k][j] += MOD;↵
}↵
}↵
piv.pb(c);↵
r++;↵
}↵
if (r == u) return {};↵
vl sol(u, 0);↵
int fv = -1;↵
FOR(c, 0, u) {↵
bool ip = false;↵
each(p, piv) if (p == c) ip = true;↵
if (!ip) { fv = c; break; }↵
}↵
sol[fv] = 1;↵
FOR(i, 0, r) sol[piv[i]] = (MOD - mt[i][fv]) % MOD;↵
↵
vvl res(R + 1, vl(D + 1, 0));↵
int c = 0;↵
FOR(o, 0, R + 1) {↵
FOR(d, 0, D + 1) {↵
res[o][d] = sol[c++];↵
}↵
}↵
return res;↵
}↵
↵
ll gp(int l, int r, const vvi& pr, ll pm) {↵
if (l > r) return pm;↵
return pr[l][r];↵
}↵
↵
vvl mul(const vvl& A, const vvl& B, int ms) {↵
vvl r(ms, vl(ms, 0));↵
FOR(i, 0, ms) {↵
FOR(k, 0, ms) {↵
if (A[i][k] == 0) continue;↵
FOR(j, 0, ms) {↵
r[i][j] += A[i][k] * B[k][j];↵
}↵
}↵
FOR(j, 0, ms) {↵
r[i][j] %= MOD;↵
}↵
}↵
return r;↵
}↵
↵
void solve() {↵
string s; ll n; cin >> s >> n;↵
int L = sz(s);↵
int R_max = 350;↵
int K = 800;↵
int target_parity = (L + n) % 2;↵
vvi pr(L, vi(L, 0));↵
ll pm = 0;↵
if (target_parity == 0) {↵
pm = 1;↵
FOR(l, 0, L) pr[l][l] = 0;↵
} else {↵
pm = 26;↵
FOR(l, 0, L) pr[l][l] = 1;↵
}↵
vl A; A.reserve(K);↵
int len = target_parity;↵
while (sz(A) < K) {↵
if (len >= L) {↵
A.pb(pr[0][L - 1]);↵
}↵
vvi cr(L, vi(L, 0));↵
ll cm = (26LL * pm) % MOD;↵
FOR(l, 0, L) {↵
FOR(r, l, L) {↵
if (s[l] == s[r]) {↵
ll v = gp(l + 1, r - 1, pr, pm);↵
v = (v + 25LL * pr[l][r]) % MOD;↵
cr[l][r] = v;↵
} else {↵
ll v = gp(l + 1, r, pr, pm);↵
v = (v + gp(l, r - 1, pr, pm)) % MOD;↵
v = (v + 24LL * pr[l][r]) % MOD;↵
cr[l][r] = v;↵
}↵
}↵
}↵
pr.swap(cr);↵
pm = cm;↵
len += 2;↵
}↵
vvl cf = guess(A, R_max, 0);↵
int tR = -1;↵
if (!cf.empty()) {↵
RFOR(i, 0, R_max + 1) {↵
if (cf[i][0] != 0) {↵
tR = i;↵
break;↵
}↵
}↵
}↵
if (tR <= 0 || cf[0][0] == 0) {↵
cout << "0";↵
return;↵
}↵
vl C(tR + 1, 0);↵
ll ic0 = ext_inv(cf[0][0], MOD);↵
FOR(o, 1, tR + 1) {↵
C[o] = (MOD - cf[o][0]) % MOD * ic0 % MOD;↵
}↵
ll m = n / 2;↵
if (m < K) {↵
cout << A[m];↵
return;↵
}↵
int ms = tR;↵
vvl M(ms, vl(ms, 0));↵
FOR(i, 0, ms) M[0][i] = C[i + 1];↵
FOR(i, 1, ms) M[i][i - 1] = 1;↵
vvl res(ms, vl(ms, 0));↵
FOR(i, 0, ms) res[i][i] = 1;↵
ll pw = m - (tR - 1);↵
vvl bs = M;↵
while (pw > 0) {↵
if (pw % 2 == 1) res = mul(res, bs, ms);↵
bs = mul(bs, bs, ms);↵
pw /= 2;↵
}↵
ll ans = 0;↵
FOR(i, 0, ms) ans = (ans + res[0][i] * A[tR - 1 - i]) % MOD;↵
cout << ans << "\n";↵
}↵
↵
int main() {↵
ios::sync_with_stdio(false);↵
cin.tie(nullptr);↵
↵
int t = 1; // cin >> t;↵
while (t--) solve();↵
return 0;↵
}↵
~~~~~↵
↵
</spoiler>↵
↵
***↵
↵
If there are any questions regarding this method or its implementation, please feel free to leave a comment or send a direct message.↵
↵
In CP, linear recurrences with **constant coefficients** are a standard topic. Such problems are typically solved by building a transition matrix and using binary exponentiation to find the $N$-th term in $O(\log N)$ time (or by applying the Bostan-Mori algorithm for larger orders).↵
↵
However, the approach must change when the coefficients are **not** constant and instead depend on $n$.↵
↵
## What is a holonomic sequence?↵
↵
A sequence $a_n$ is called **holonomic** (or **P-recursive**) if it satisfies a linear recurrence relation where the coefficients are *polynomials* in $n$.↵
↵
Formally, there exist polynomials $P_0(x), P_1(x), \dots, P_k(x)$ (where $P_k \neq 0$) such that for all $n$:↵
↵
$$ P_k(n) a_{n+k} + P_{k-1}(n) a_{n+k-1} + \dots + P_0(n) a_n = 0 $$↵
↵
The integer $k$ is the **order** of the recurrence, and the maximum degree among the polynomials $P_i$ is the **degree** of the recurrence.↵
↵
**Classic Examples:**↵
↵
- **Factorials**: $1 \cdot a_n - n \cdot a_{n-1} = 0$. (Order 1, Degree 1).↵
- **Catalan numbers**: $(n+2) \cdot C_{n+1} - (4n+2) \cdot C_n = 0$. (Order 1, Degree 1).↵
- **Derangements**: $1 \cdot D_n - (n-1) \cdot D_{n-1} - (n-1) \cdot D_{n-2} = 0$. (Order 2, Degree 1).↵
↵
## Applications in CP↵
↵
A key property of holonomic sequences is their **closure** under various operations. ↵
If sequences $A$ and $B$ are holonomic, then the following sequences are *also* holonomic:↵
↵
1. $A + B$↵
2. $A \cdot B$↵
3. Prefix sums: $\sum_{i=0}^n A_i$↵
↵
This implies that for complex sums such as $\sum_{k=0}^n \binom{n}{k}^2 \binom{n+k}{k}^2$ (the Apery numbers), a polynomial recurrence is mathematically guaranteed to exist, as binomial coefficients and factorials are themselves holonomic.↵
↵
Analytically, these recurrences can be found using **Zeilberger's Algorithm** (Creative Telescoping), which algebraically simplifies the summation. ↵
↵
However, implementing Zeilberger's algorithm during a contest is highly impractical. Since it is theoretically guaranteed that a recurrence exists, one can bypass the analytical derivation and instead **guess the recurrence** programmatically.↵
↵
## Automated Guessing with Gaussian Elimination↵
↵
Consider a scenario where the $N$-th term of a sequence must be found in $O(N)$ time (for $N \le 10^7$), but the available direct calculation or dynamic programming approach takes $O(N^2)$. Linear algebra can be used to automatically deduce the underlying recurrence.↵
↵
Assume the recurrence has a small order $R$ (e.g., $R=2$) and polynomial degree $D$ (e.g., $D=1$). The equation takes the following form:↵
↵
$$ (c_{2,1} n + c_{2,0}) a_n + (c_{1,1} n + c_{1,0}) a_{n-1} + (c_{0,1} n + c_{0,0}) a_{n-2} = 0 $$↵
↵
Given that the values of $a_n, a_{n-1}, a_{n-2}$, and $n$ are known, the only unknowns are the constants $c_{i,j}$.↵
There are exactly $U = (R+1) \times (D+1)$ unknown constants. In this example, $U = 3 \times 2 = 6$.↵
↵
## Reference Implementation (C++)↵
↵
Below is a reference **solution for the problem 506E (Mr. Kitayuta's Gift)** utilizing this algorithm.↵
↵
**Problem 506E:** *Given a string $S$ ($|S| \le 200$), calculate the number of valid ways to insert exactly $N$ characters ($N \le 10^9$) such that the resulting string is a palindrome. All calculations should be modulo $10007$.*↵
↵
**How my solution works:**↵
↵
1. **Naive DP:** We write a simple $O(|S|^2 \cdot K)$ DP to compute the answers for small lengths.↵
2. **Parity Split (Crucial):** Even and odd length palindromes behave like two independent sequences. Mixing them inflates the recurrence order to $\sim 600$, causing a Time Limit Exceeded (TLE) during matrix exponentiation. By exclusively computing terms for the target parity $(|S| + N) \pmod 2$, we halve the recurrence order to $\le 305$.↵
3. **Automated Guessing:** We generate $K = 800$ terms and feed them into the Gaussian elimination algorithm with polynomial degree $D=0$ (which effectively finds a constant-coefficient recurrence).↵
4. **Optimized Matrix Exponentiation:** We construct the transition matrix and raise it to the power of $N/2$. Since $MOD = 10007$, we can safely omit the modulo operation in the innermost loop of the matrix multiplication, allowing the solution to comfortably run in $\sim 0.3s$.↵
↵
***↵
↵
<spoiler summary="Code for 506E">↵
↵
~~~~~↵
#include <bits/stdc++.h>↵
using namespace std;↵
↵
using ll = long long;↵
using ull = unsigned long long;↵
using ld = long double;↵
using pii = pair<int, int>;↵
using pll = pair<ll, ll>;↵
using vi = vector<int>;↵
using vl = vector<ll>;↵
using vvi = vector<vector<int>>;↵
using vvl = vector<vector<ll>>;↵
↵
#define all(x) (x).begin(), (x).end()↵
#define rall(x) (x).rbegin(), (x).rend()↵
#define sz(x) (int)(x).size()↵
#define pb push_back↵
#define mp make_pair↵
#define fi first↵
#define se second↵
#define FOR(i, a, b) for (int i = (a); i < (b); ++i)↵
#define RFOR(i, a, b) for (int i = (b)-1; i >= (a); --i)↵
#define each(x, a) for (auto &x : a)↵
↵
const int MOD = 10007;↵
const int INF = 1e9;↵
const ll LINF = 1e18;↵
↵
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }↵
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }↵
↵
ll bp(ll a, ll b, ll mod = MOD) {↵
ll r = 1;↵
a %= mod;↵
while (b > 0) {↵
if (b % 2 == 1) r = (r * a) % mod;↵
a = (a * a) % mod;↵
b /= 2;↵
}↵
return r;↵
}↵
↵
ll ext_inv(ll a, ll m) {↵
ll m0 = m, y = 0, x = 1;↵
if (m == 1) return 0;↵
while (a > 1) {↵
ll q = a / m;↵
ll t = m;↵
m = a % m, a = t;↵
t = y;↵
y = x - q * y;↵
x = t;↵
}↵
if (x < 0) x += m0;↵
return x;↵
}↵
↵
vvl guess(const vl& a, int R, int D) {↵
int u = (R + 1) * (D + 1);↵
int eq = sz(a) - R;↵
if (eq < u) return {};↵
vvi mt(eq, vi(u, 0));↵
FOR(i, 0, eq) {↵
int n = i + R;↵
int c = 0;↵
FOR(o, 0, R + 1) {↵
ll p = 1;↵
FOR(d, 0, D + 1) {↵
mt[i][c] = (p * a[n - o]) % MOD;↵
c++;↵
p = (p * n) % MOD;↵
}↵
}↵
}↵
int r = 0;↵
vi piv;↵
FOR(c, 0, u) {↵
if (r >= eq) break;↵
int p = r;↵
while (p < eq && mt[p][c] == 0) p++;↵
if (p == eq) continue;↵
swap(mt[r], mt[p]);↵
ll iv = ext_inv(mt[r][c], MOD);↵
FOR(j, c, u) mt[r][j] = (mt[r][j] * iv) % MOD;↵
FOR(k, 0, eq) {↵
if (k == r || mt[k][c] == 0) continue;↵
int f = mt[k][c];↵
for (int j = c; j < u; ++j) {↵
mt[k][j] = (mt[k][j] - f * mt[r][j]) % MOD;↵
if (mt[k][j] < 0) mt[k][j] += MOD;↵
}↵
}↵
piv.pb(c);↵
r++;↵
}↵
if (r == u) return {};↵
vl sol(u, 0);↵
int fv = -1;↵
FOR(c, 0, u) {↵
bool ip = false;↵
each(p, piv) if (p == c) ip = true;↵
if (!ip) { fv = c; break; }↵
}↵
sol[fv] = 1;↵
FOR(i, 0, r) sol[piv[i]] = (MOD - mt[i][fv]) % MOD;↵
↵
vvl res(R + 1, vl(D + 1, 0));↵
int c = 0;↵
FOR(o, 0, R + 1) {↵
FOR(d, 0, D + 1) {↵
res[o][d] = sol[c++];↵
}↵
}↵
return res;↵
}↵
↵
ll gp(int l, int r, const vvi& pr, ll pm) {↵
if (l > r) return pm;↵
return pr[l][r];↵
}↵
↵
vvl mul(const vvl& A, const vvl& B, int ms) {↵
vvl r(ms, vl(ms, 0));↵
FOR(i, 0, ms) {↵
FOR(k, 0, ms) {↵
if (A[i][k] == 0) continue;↵
FOR(j, 0, ms) {↵
r[i][j] += A[i][k] * B[k][j];↵
}↵
}↵
FOR(j, 0, ms) {↵
r[i][j] %= MOD;↵
}↵
}↵
return r;↵
}↵
↵
void solve() {↵
string s; ll n; cin >> s >> n;↵
int L = sz(s);↵
int R_max = 350;↵
int K = 800;↵
int target_parity = (L + n) % 2;↵
vvi pr(L, vi(L, 0));↵
ll pm = 0;↵
if (target_parity == 0) {↵
pm = 1;↵
FOR(l, 0, L) pr[l][l] = 0;↵
} else {↵
pm = 26;↵
FOR(l, 0, L) pr[l][l] = 1;↵
}↵
vl A; A.reserve(K);↵
int len = target_parity;↵
while (sz(A) < K) {↵
if (len >= L) {↵
A.pb(pr[0][L - 1]);↵
}↵
vvi cr(L, vi(L, 0));↵
ll cm = (26LL * pm) % MOD;↵
FOR(l, 0, L) {↵
FOR(r, l, L) {↵
if (s[l] == s[r]) {↵
ll v = gp(l + 1, r - 1, pr, pm);↵
v = (v + 25LL * pr[l][r]) % MOD;↵
cr[l][r] = v;↵
} else {↵
ll v = gp(l + 1, r, pr, pm);↵
v = (v + gp(l, r - 1, pr, pm)) % MOD;↵
v = (v + 24LL * pr[l][r]) % MOD;↵
cr[l][r] = v;↵
}↵
}↵
}↵
pr.swap(cr);↵
pm = cm;↵
len += 2;↵
}↵
vvl cf = guess(A, R_max, 0);↵
int tR = -1;↵
if (!cf.empty()) {↵
RFOR(i, 0, R_max + 1) {↵
if (cf[i][0] != 0) {↵
tR = i;↵
break;↵
}↵
}↵
}↵
if (tR <= 0 || cf[0][0] == 0) {↵
cout << "0";↵
return;↵
}↵
vl C(tR + 1, 0);↵
ll ic0 = ext_inv(cf[0][0], MOD);↵
FOR(o, 1, tR + 1) {↵
C[o] = (MOD - cf[o][0]) % MOD * ic0 % MOD;↵
}↵
ll m = n / 2;↵
if (m < K) {↵
cout << A[m];↵
return;↵
}↵
int ms = tR;↵
vvl M(ms, vl(ms, 0));↵
FOR(i, 0, ms) M[0][i] = C[i + 1];↵
FOR(i, 1, ms) M[i][i - 1] = 1;↵
vvl res(ms, vl(ms, 0));↵
FOR(i, 0, ms) res[i][i] = 1;↵
ll pw = m - (tR - 1);↵
vvl bs = M;↵
while (pw > 0) {↵
if (pw % 2 == 1) res = mul(res, bs, ms);↵
bs = mul(bs, bs, ms);↵
pw /= 2;↵
}↵
ll ans = 0;↵
FOR(i, 0, ms) ans = (ans + res[0][i] * A[tR - 1 - i]) % MOD;↵
cout << ans << "\n";↵
}↵
↵
int main() {↵
ios::sync_with_stdio(false);↵
cin.tie(nullptr);↵
↵
int t = 1; // cin >> t;↵
while (t--) solve();↵
return 0;↵
}↵
~~~~~↵
↵
</spoiler>↵
↵
***↵
↵
If there are any questions regarding this method or its implementation, please feel free to leave a comment or send a direct message.↵



