题目链接
题目描述
给定长度为n的数组a,定义一次操作为:
1. 算出长度为n的数组s,使得s i = (a[1] + a[2] + … + a[i]) mod 1,000,000,007;
2. 执行a = s;
现在问k次操作以后a长什么样。
输入描述:
第一行两个整数n,k(1 <= n <= 2000, 0 <= k <= 1,000,000,000);
第二行n个整数表示a数组(0 <= a i <= 1,000,000,000)。
输出描述:
一行n个整数表示答案。
示例1
输入
3 1
1 2 3
输出
1 3 6
示例2
输入
5 0
3 14 15 92 6
输出
3 14 15 92 6
对没错,这道题就是可以用矩阵快速幂来做:
【快速幂的公式推导】
为了放在矩阵上操作,把s看成一个序列而不是一个和。
最开始时,s[n]是 a[1], a[2], a[3], …,a[n]
一次操作以后,s[n]是a[1], a[1]+a[2], …, a[1]+···+a[n]
可以把s用矩阵M这样记录:每一列从上往下表示s[i]有几个a[1], a[2], …,a[n]
以n=5的情况举例,那么起始的s就是这样的
每进行一次操作,s[i]含有的a[1], a[2], …,a[n]的数量,都将变成s[0~i]含有的a[1], a[2], … a[n]的和
一次操作后,s变为:
【后续优化】
虽然公式推导出来了,但这道题矩阵的n高达2000,每做一次O(n 3 )的矩阵乘法需要2000*2000*2000=8e9,就算快速幂把k=1e9次乘法优化到log k,也依然会超时,所以还得优化。
通过观察发现,在这道题里,数值的累加是有规律的,导致s的状态非常特殊:每一列都是最后一列的一部分,并且其他地方都是0。
所以,黑科技来了——计算矩阵乘法的时候,只计算出最后一列就可以了!这是O(n 2 )的复杂度!
等一下——如果只需要计算最后一列,而整个矩阵的数据也只从最后一列获取,那直接只保存最后一列好了??也就是说我们可以把矩阵封装为一个大小只有n的数组,对原矩阵中任何非0的位置的访问,都转化为对这个数组的访问,对原来矩阵是0的位置的访问,就返回0(事实上根本不用访问0位置)。
矩阵访问位置的转化是线性对应关系,实现起来很容易,一个宏就搞定了:#define conv(x,y) (n-(y)-1+x)
上个图稍微推导一下
以上,就是我瞎jb写出来的矩阵快速幂
#include
#include
#define conv(x,y) (n-(y)-1+x)
#define N_max 2003
#define mod 1000000007
int n,k;
int ipt[N_max];
unsigned long long temp;
typedef int Matrix[N_max];
//矩阵拷贝,直接把数组拷贝过去
const int sz = N_max * sizeof(int);
#define mc(x,y) memcpy(x,y,sz);
#define ms(x) memset(x,0,sz);
//矩阵乘法,只计算最后一列,并把结果保存在a中
void mul(Matrix a,Matrix m0) {
Matrix res= { 0 };
for (int i = 0; i < n; ++i) {
//j=n-1;
int _v = 0;
for (int k = i; k < n; ++k) {
//防止乘法溢出
temp = a[conv(i, k)];
temp = temp*m0[conv(k, n - 1)]%mod;
_v = (_v + temp) % mod;
}
res[conv(i,n-1)] = _v;
}
mc(a, res);
}
//矩阵快速幂
Matrix res_mi;
void quickmi(Matrix _a, int k) {
Matrix a;
mc(a, _a);
if (k > 0) {
mc(res_mi, a);
k -- ;
}
while (k>0)
{
if (k % 2 == 1)
mul(res_mi, a);
mul(a, a);
k = (k >> 1);
}
}
/*
求这个矩阵的k次方
[ 1 1 1 1 ]
0 1 1 1
0 0 1 1
0 0 0 1
*/
int main() {
Matrix c;
scanf("%d %d", &n, &k);
for (int i = 0; i < n; ++i) {
scanf("%d", &ipt[i]);
//初始化矩阵c
for (int j = i; j < n; ++j)
c[conv(i,j)] = 1;
}
if (k == 0)//k==0时快速幂一次都不会做导致结果为0,在这里直接处理了
{
for (int i = 0; i < n; ++i)
printf("%d%c", ipt[i], i == n - 1 ? '\n' : ' ');
return 0;
}
quickmi(c, k);
for (int j= 0; j < n; ++j) {
temp = 0;
for (int i = 0; i <= j; ++i)
temp =(temp + res_mi[conv(i, j)]*ipt[i]%mod)%mod;
printf("%llu%c", temp, j == n - 1 ? '\n' : ' ');
}
}
【总结】
矩阵压缩是一个什么操作?估计又是想复杂了。
写的很乱,大概没人看吧?还是等一波大佬的题解,溜了溜了。