题意: 递推数列求和
给一个有
n
个数的可重集
2⩽n⩽100000,1⩽k⩽1000000000.
符号定义
符号 | 说明 |
---|---|
a,b | 假设 a⩾b |
{di} |
di=⎧⎩⎨⎪⎪aa+bdi−1+di−2(i=0)(i=1)(i>1)
|
Si |
Si={原可重集元素和Si−1+di(i=0)(i>0)
|
方法一: 利用周期性
因为是对MOD=10000007取余,所以
di
肯定存在一个值不大于MOD的周期c,即有
di+c=di
.(请思考并理解这段话后再看后文)
对应代码中的第44~52行
c的算法:利用 di 的递推式计算,当出现 di=a且di−1=b 时,此时的i就是c的值.
用s1存储一段周期的元素和.
这段代码结束后,有两种情况:①循环正常结束,表示k比较小,还不用算出c,已经得到答案了.②循环被break,表示此时的i即为c的值.
两种情况可以用第51~59行的代码统一处理.
第5~14行是读入外挂,第27~37行是在数组中找出最大的两个数的一种方法.
#include <algorithm>
#include <iostream>
using namespace std;
#define ISDIG ((c=getchar())>='0'&&c<='9')
template <typename T>
inline int read(T& x) {
int c, sign = 1; x = 0;
while (!(ISDIG || c == '-')) if (c == EOF) return c;
if (c == '-') sign = -1; else x = x*10 + c - '0';
while (ISDIG) x = x*10 + c - '0';
x *= sign;
return 1;
}
const int MOD = 10000007;
int d[MOD + 10];
int main() {
int n, k, a, b;
int t, i; // 临时变量、计数器
while (cin >> n >> k) {
read(a); read(b);
int s = a + b;
for (i = 2; i < n; i++) {
//{找出初始S中最大的两个数a,b
read(t);
if (t > a) {
if (t > b) {
if (a < b) a = t;
else b = t;
}
else a = t;
}
else if (t > b) b = t;
//}
s = (s + t) % MOD;
}
if (b > a) swap(a , b); // 假设a>=b
d[0] = a;
d[1] = a + b;
//{计算出周期c和这段周期的元素和s1
int c, s1 = d[1];
for (i = 2; i <= k; i++) {
d[i] = (d[i-1] + d[i-2]) % MOD;
s1 = (d[i] + s1) % MOD;
if (d[i] == a && d[i-1] == b) break;
}
c = i;
//}
do {
s = (s + s1) % MOD;
k -= c;
} while (k > c);
// 去掉完整的周期后,还有些残余的末尾项要加上
for (i = 1; i <= k; i++) s = (d[i] + s) % MOD;
cout << s << "\n";
}
return 0;
}
注意也只有64MB的内存限制才可以用这个方法,因为32MB只能开辟800万大小的一个int数组╮(╯▽╰)╭.
方法二: 矩阵快速幂模
矩阵快速幂模的解法原理,我就不讲这么详细了.去年做过这道比较基础的矩阵快速幂模题:FOJ 1683 纪念SlingShot,那道题理解后,此类题型的基本思想就掌握了,这题难度差不多.
FOJ 1692 Key problem这篇博客讲了道更难的矩阵快速幂模题.
这题关键是推出如下的公式:
⎛⎝⎜100111110⎞⎠⎟k⎛⎝⎜S0ab⎞⎠⎟=⎛⎝⎜Skdkdk−1⎞⎠⎟
借着做这题的机会,开发个新的矩阵类.
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
#define ISDIG ((c=getchar())>='0'&&c<='9')
template <typename T>
inline int read(T& x) {
int c, sign = 1; x = 0;
while (!(ISDIG || c == '-')) if (c == EOF) return c;
if (c == '-') sign = -1; else x = x*10 + c - '0';
while (ISDIG) x = x*10 + c - '0';
x *= sign;
return 1;
}
/*矩阵类(用于快速幂模)
1、T是整型类型,N是方阵大小,MOD是取余的值
2、Eye是单位阵
*/
template <typename T, const int N, const int MOD>
class Matrix {
T val[N][N];
public:
Matrix() { memset(val, 0, sizeof(val)); }
Matrix(T a[N][N]) { memcpy(val, a, sizeof(val)); }
Matrix operator*(const Matrix& c) const {
Matrix res;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
for (int k = 0; k < N; ++k) {
res.val[i][j] += val[i][k] * c.val[k][j];
//防止矩阵元素变为负数,若不需要,去掉"+MOD"
res.val[i][j] = (res.val[i][j] + MOD) % MOD;
}
return res;
}
Matrix& operator*=(const Matrix& c) {
*this = *this * c;
return *this;
}
Matrix operator^(int k) const { //返回*this^k
Matrix res = Eye();
Matrix step(*this);
while (k) {
if (k & 1) res *= step;
k >>= 1;
step *= step;
}
return res;
}
Matrix Eye() const {
Matrix a;
for (int i = 0; i < N; i++) a.val[i][i] = 1;
return a;
}
void out() const {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
cout << val[i][j] << " ";
cout << "\n";
}
}
T* operator[](int i) { return val[i]; }
};
typedef long long LL;
const int MOD = 10000007;
int main() {
int n, k, a, b, t;
while (cin >> n >> k) {
read(a); read(b);
int s = a + b;
for (int i = 2; i < n; i++) {
//{找出初始S中最大的两个数a,b
read(t);
if (t > a) {
if (t > b) {
if (a < b) a =t;
else b = t;
}
else a = t;
}
else if (t > b) b = t;
//}
s = (s + t) % MOD;
}
if (b > a) swap(a, b); // 假设a>=b
LL aa[3][3]={{1,1,1},{0,1,1},{0,1,0}};
Matrix<LL,3,MOD> A(aa), X, Y;
X[0][0] = s, X[1][0] = a, X[2][0] = b;
Y = (A^k) * X;
cout << Y[0][0] << "\n";
}
return 0;
}