这是一个比较水但是需要一定码力才能码出来的题。
题目链接
点击打开链接
题目解法
考虑到这是个递推式,因此采用矩阵优化地推。那么构造以下算式:
\[\begin{pmatrix}f_{0}&f_{1}\end{pmatrix}\cdot \begin{pmatrix}1&s_0\\0&s_1\\\end{pmatrix}\cdots\begin{pmatrix}1&s_n\\0&s_{n+1}\end{pmatrix}=\begin{pmatrix}f_{n+1}& f_{n+2}\end{pmatrix} \]
所以我们只需要求一大堆矩阵的乘积。
由于这是循环的,所以很多地方可以直接当成一个循环节的矩阵积的若干次方。有时还要乘上前缀积与后缀积。不过这样你会发现你还需要乘上区间积:如果两个修改在同一个区间内,那么这两个修改之间的矩阵乘积还需要计算一下,但是你发现使用前缀积计算是不成立的,因为不一定每个前缀积都可以求逆。
所以你需要一个线段树维护区间乘积。时间复杂度 \(\mathcal O(m(\log k+\log n))\) 。不过这种方法我没有写过,而且也没看到任何题解这么写的,因此我怀疑有一些我没考虑到的问题,各位可以尝试这样写一下。
不过还有另外一种方法(这种方法可能有一大堆细节问题需要考虑):
不带修改的周期可以直接快速幂。考虑用线段树维护有修改的周期,每次在线段树上单点修改一个矩阵,然后再求整体乘积。
下面代码是这种方法。
代码
#include
#include
#include
using namespace std;
typedef long long ll;
const ll NM = 5e4 + 5;
typedef long long ll;
ll k, mod, n, m;
ll ml(ll x, ll y) { return 1ll * x * y % mod; }
ll ad(ll x, ll y) { return (x + y > mod) ? (x + y - mod) : (x + y); }
ll dc(ll x, ll y) { return (x - y < 0) ? (x - y + mod) : (x - y); }
struct Mat {
ll A[2][2];
Mat() {}
ll * operator [] (ll i) { return A[i]; }
const ll * operator [] (ll i) const { return A[i]; }
void Init() {
A[0][0] = 1;
A[0][1] = 0;
A[1][0] = 0;
A[1][1] = 1;
}
void Clear() { memset(A, 0, sizeof(A)); }
void print() {
for (ll i = 0; i < 2; ++i) {
printf(" ");
for (ll j = 0; j < 2; ++j)
printf("%lld ", A[i][j]);
printf("\n");
}
}
};
struct LYK {
ll i, x, y, v;
LYK() {}
LYK(ll a, ll b, ll c, ll d) : i(a), x(b), y(c), v(d) {}
} szj[NM << 1];
struct SZJ {
ll i;
Mat A;
} chg[NM << 1];
bool cmp(const LYK &x, const LYK &y) { return x.i < y.i; }
Mat operator * (const Mat &x, const Mat &y) {
Mat ret;
ret.Clear();
for (ll i = 0; i < 2; ++i)
for (ll j = 0; j < 2; ++j)
for (ll k = 0; k < 2; ++k)
ret[i][j] = ad(ret[i][j], ml(x[i][k], y[k][j]));
return ret;
}
ll s[NM], ct, mt;
ll blk[NM << 1];
Mat ltg[NM], bas;
void ksm(Mat &x, Mat y, ll c) {
if (!c) return ;
for (; c; c >>= 1, y = y * y)
if (c & 1) x = x * y;
}
Mat tr[NM << 2];
void Upd(ll o) { tr[o] = tr[o << 1] * tr[o << 1 | 1]; }
void Build(ll o, ll sl, ll sr) {
if (sl == sr) {
tr[o] = ltg[sl];
return ;
}
ll mid = (sl + sr) >> 1;
Build(o << 1, sl, mid);
Build(o << 1 | 1, mid + 1, sr);
Upd(o);
}
void Modify(ll o, ll sl, ll sr, ll ps, Mat v) {
if (sl == sr) {
tr[o] = v;
return ;
}
ll m = (sl + sr) >> 1;
if (ps <= m) Modify(o << 1, sl, m, ps, v);
else Modify(o << 1 | 1, m + 1, sr, ps, v);
Upd(o);
}
int main() {
scanf("%lld%lld", &k, &mod);
if (k == 0) printf("0\n");
if (k == 1) printf("%d\n", mod != 1);
if (k == 0 || k == 1) return 0;
--k;
scanf("%lld", &n);
for (ll i = 0; i < n; ++i) {
scanf("%lld", &s[i]);
s[i] = s[i] % mod;
}
for (ll i = 0; i < n; ++i) {
ltg[i][0][0] = 0;
ltg[i][0][1] = s[i];
ltg[i][1][0] = 1;
ltg[i][1][1] = s[(i == n - 1) ? 0 : (i + 1)];
}
bas.Init(); //base的初始化
for (ll i = 0; i < n; ++i)
bas = bas * ltg[i];
scanf("%lld", &m);
for (ll i = 1; i <= m; ++i) {
ll j, v;
scanf("%lld%lld", &j, &v);
szj[++ct] = LYK(j, 0, 1, v);
szj[++ct] = LYK(j - 1, 1, 1, v);
}
sort(szj + 1, szj + ct + 1, cmp);
for (ll i = 1; i <= ct; ++i) {
if (i != 1 && szj[i].i == szj[i - 1].i)
chg[mt].A[szj[i].x][szj[i].y] = szj[i].v;
else {
chg[++mt].A = ltg[szj[i].i % n];
chg[mt].A[szj[i].x][szj[i].y] = szj[i].v;
chg[mt].i = szj[i].i;
}
}
for (ll i = 1; i <= mt; ++i) blk[i] = chg[i].i / n;
Mat ans;
ans.Clear();
ans[0][0] = 0;
ans[0][1] = 1;
ll ed = -1;
Build(1, 0, n - 1);
ll i, lst = -1, finb = k / n;
for (i = 1; i <= mt; ) {
if (blk[i] >= finb)
break ;
ksm(ans, bas, blk[i] - lst - 1);
ll j = i;
for (; blk[i] == blk[j]; ++i)
Modify(1, 0, n - 1, chg[i].i % n, chg[i].A);
ans = ans * tr[1];
for (ll tt = j; tt < i; ++tt)
Modify(1, 0, n - 1, chg[tt].i % n, ltg[chg[tt].i % n]);
lst = blk[i - 1];
ed = (lst + 1) * n - 1;
}
ll nbblk = (k - ed) / n;
if (k % n == n - 1)
--nbblk;
ksm(ans, bas, nbblk);
ed = ed + nbblk * n;
ll res = k - ed;
ll pt = i;
for (i = 1; i <= res; ++i) {
if (pt <= mt && chg[pt].i == ed + i)
ans = ans * chg[pt++].A;
else ans = ans * ltg[i - 1];
}
printf("%lld\n", ans[0][0]);
return 0;
}
总结
- 嗯...主要在调代码吧。写之前要先想好边界如何处理。