【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; }