牛客练习赛17 C 操作数(矩阵快速幂)

题目

题目链接

题目描述

给定长度为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就是这样的

1000001000001000001000001

每进行一次操作,s[i]含有的a[1], a[2], …,a[n]的数量,都将变成s[0~i]含有的a[1], a[2], … a[n]的和
一次操作后,s变为:

1000011000111001111011111

两次操作后,s变为
1000021000321004321054321

由上述性质,结合矩阵乘法的原理可知,每一次操作,要把s[i]左边全部加起来,相当于给s右乘一个
1000011000111001111011111

最终的s表示为
1000001000001000001000001×1000011000111001111011111k=1000011000111001111011111k

【后续优化】
虽然公式推导出来了,但这道题矩阵的n高达2000,每做一次O(n 3 )的矩阵乘法需要2000*2000*2000=8e9,就算快速幂把k=1e9次乘法优化到log k,也依然会超时,所以还得优化。

通过观察发现,在这道题里,数值的累加是有规律的,导致s的状态非常特殊:每一列都是最后一列的一部分,并且其他地方都是0。

121321432154321

是不是有一种在把最后一列向上移动的感觉?这意味着,如果知道了矩阵的最后一列,那整个矩阵就都知道了!

所以,黑科技来了——计算矩阵乘法的时候,只计算出最后一列就可以了!这是O(n 2 )的复杂度!

等一下——如果只需要计算最后一列,而整个矩阵的数据也只从最后一列获取,那直接只保存最后一列好了??也就是说我们可以把矩阵封装为一个大小只有n的数组,对原矩阵中任何非0的位置的访问,都转化为对这个数组的访问,对原来矩阵是0的位置的访问,就返回0(事实上根本不用访问0位置)。

矩阵访问位置的转化是线性对应关系,实现起来很容易,一个宏就搞定了:#define conv(x,y) (n-(y)-1+x)

上个图稍微推导一下

牛客练习赛17 C 操作数(矩阵快速幂)_第1张图片

以上,就是我瞎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' : ' ');
    }
}

【总结】
矩阵压缩是一个什么操作?估计又是想复杂了。
写的很乱,大概没人看吧?还是等一波大佬的题解,溜了溜了。

你可能感兴趣的:(acm)