【bzoj4547】Hdu5171 小奇的集合 矩阵乘法

题目描述

 有一个大小为n的可重集S,小奇每次操作可以(此处“可以”指的是“必须”)加入一个数a+b(a,b均属于S),求k次操作后它可获得的S的和的最大

值。(数据保证这个值为非负数)

输入

第一行有两个整数n,k表示初始元素数量和操作数,第二行包含n个整数表示初始时可重集的元素。

对于100%的数据,有 n<=10^5,k<=10^9,|ai|<=10^5

输出

输出一个整数,表示和的最大值。答案对10000007取模。

样例输入

2 2
3 6

样例输出

33


题解

矩阵乘法

显然每次选择集合中最大的两个数相加即可。

如果最大的两个数都是正数,那么结果显然也是正数并且比它们都要大,即$(b,a)\to(a,a+b)$,所以可以使用矩阵乘法来解决。

具体过程:$\begin{bmatrix}a&b&s\end{bmatrix}*\begin{bmatrix}1&1&1\\1&0&0\\0&0&1\end{bmatrix}=\begin{bmatrix}a+b&a&s+a\end{bmatrix}$

如果最大的两个数都是负数,那么结果显然也是负数并且比它们都要小,因此直接选择它们相加k次即可。

如果这两个数一正一负,那么每次负数会变大,由于数的范围只有$10^5$,因此可以暴力操作直到加到正数或者没有操作机会,然后矩乘即可。

时间复杂度$O(a+\log k)$

#include <cstdio>
#include <cstring>
#include <algorithm>
#define mod 10000007
using namespace std;
typedef long long ll;
struct data
{
    ll v[3][3];
    data(int x = 0) {memset(v , 0 , sizeof(v)) , v[0][0] = v[1][1] = v[2][2] = x;}
    ll *operator[](int a) {return v[a];}
    data operator*(data a)
    {
        data ans;
        int i , j , k;
        for(i = 0 ; i < 3 ; i ++ )
            for(j = 0 ; j < 3 ; j ++ )
                for(k = 0 ; k < 3 ; k ++ )
                    ans[i][j] = (ans[i][j] + v[i][k] * a[k][j]) % mod;
        return ans;
    }
}A;
int a[100010];
data pow(data x , int y)
{
    data ans(1);
    while(y)
    {
        if(y & 1) ans = ans * x;
        x = x * x , y >>= 1;
    }
    return ans;
}
int main()
{
    int n , k , i;
    ll sum = 0;
    scanf("%d%d" , &n , &k);
    for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]) , sum += a[i];
    sort(a + 1 , a + n + 1);
    if(a[n] <= 0) printf("%lld\n" , ((sum + (ll)(a[n] + a[n - 1]) * k) % mod + mod) % mod);
    else
    {
        for(sum -= a[n] ; k && a[n - 1] < 0 ; k -- )
            a[n - 1] += a[n] , sum += a[n - 1];
        A[0][0] = A[0][1] = A[0][2] = A[1][0] = A[2][2] = 1 , A = pow(A , k + 1);
        printf("%lld\n" , ((sum + a[n] * A[0][2] + a[n - 1] * A[1][2]) % mod + mod) % mod);
    }
    return 0;
}

 

posted @ 2017-10-19 14:04  GXZlegend  阅读(599)  评论(0编辑  收藏  举报