Luogu P4910 帕秋莉的手环 题解 [ 绿 ] [ 矩阵快速幂 ] [ 环形 DP ]
帕秋莉的手环:环形矩乘 DP 板子。
DP 设计是显然的,\(dp_{i,0/1}\) 表示当前是第 \(i\) 个珠子,是否为金属性的方案数。转移为:
- \(dp_{i, 0} = dp_{i-1, 1}\)。
- \(dp_{i, 1} = dp_{i-1, 0} + dp_{i-1, 1}\)。
因为第二维状态数很少,且转移方程只涉及加法,所以考虑矩阵加速。
关键在于如何处理环形。我们知道一个经典结论:对于一张图而言,单位矩阵乘邻接矩阵的 \(k\) 次幂,就能求出图上任意一点走 \(k\) 步到任意一点的方案数。其中矩阵的第 \(i\) 行第 \(j\) 列表示 \(i\to j\) 的方案数。因为可以把单位矩阵看作是 DP 的初始状态,而邻接矩阵相当于对 DP 数组进行一次整体转移。
对于此题也是同理,我们把 DP 状态的第二维看做一张图,因为是环形,所以要保证从 \(i\) 点走 \(n\) 步后回到 \(i\) 点,于是直接单位矩阵乘转移矩阵的 \(n\) 次幂,答案就是矩阵主对角线上的和。这种做法将 DP 转化为图论考虑,能够避免大量分类讨论,简化代码。
时间复杂度 \(O(tB^3\log n)\),其中 \(B = 2\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const ll mod = 1000000007;
struct Matrix{
ll a[2][2];
Matrix(){ memset(a, 0, sizeof(a)); }
Matrix operator * (const Matrix & t) const{
Matrix res;
for(int i = 0; i < 2; i++)
for(int k = 0; k < 2; k++)
for(int j = 0; j < 2; j++)
res.a[i][j] = (res.a[i][j] + a[i][k] * t.a[k][j]) % mod;
return res;
}
}dp;
Matrix qpow(Matrix a, ll b)
{
Matrix res;
res.a[0][0] = res.a[1][1] = 1;
while(b)
{
if(b & 1) res = res * a;
b >>= 1;
a = a * a;
}
return res;
}
ll n;
void solve()
{
cin >> n;
dp.a[0][0] = 0;
dp.a[0][1] = dp.a[1][1] = dp.a[1][0] = 1;
dp = qpow(dp, n);
cout << (dp.a[0][0] + dp.a[1][1]) % mod << '\n';
}
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号