【BZOJ4547】小奇的集合-矩阵快速幂+等比二分

测试地址:小奇的集合
做法:本题需要用到矩阵快速幂+等比二分。
首先,我们肯定是每次选择集合中两个最大的元素加起来,这样能使最后集合的和最大。
那么现在有两种情况:
1.这两个元素加起来比原最大值小,但比原次大值大,这时候我们把次大值替换为它们的和。
2.这两个元素加起来比原最大值大,这时候次大值就是原最大值,最大值就是它们的和。
为什么不会出现两个元素加起来比原次大值还小的情况?因为题目保证最后结果不是负数,而出现这种情况的条件是最大值和次大值都是负数,这显然是不可能出现的。
对于第二种情况,我们可以构造出转移矩阵A,然后要求的就是A+A2+A3+...+Ak与矩阵[maxsecond_max]T的乘积中的第一项,求前面那一串东西可以用等比二分的思想加速,这个思想是这样的:
令要求的式子为f(k),则:
k为偶数,则f(k)=f(k/2)+Ak/2f(k/2)
k为奇数,则f(k)=f(k1)+Ak
显然这个算法搭配矩阵快速幂是O(log2k)的,那么就可以解决第二种情况。
而对于第一种情况,我们知道这种情况只会重复不超过权值次,当次大值变为非负数的时候,就变为了第二种情况,所以我们直接模拟这一段过程,再用上面的方法做剩下的部分即可。
以下是本人代码:

#include <bits/stdc++.h>
typedef long long ll;
const ll mod=10000007;
int n;
ll k,mx1=-1000000000,mx2=-1000000000,sum=0;
struct matrix
{
    ll mat[2][2];
}M[35],Ans,E,Zero;

matrix operator * (matrix A,matrix B)
{
    matrix S;
    memset(S.mat,0,sizeof(S.mat));
    for(int i=0;i<=1;i++)
        for(int j=0;j<=1;j++)
            for(int k=0;k<=1;k++)
                S.mat[i][j]=(S.mat[i][j]+A.mat[i][k]*B.mat[k][j])%mod;
    return S;
}

matrix operator + (matrix A,matrix B)
{
    matrix S;
    for(int i=0;i<=1;i++)
        for(int j=0;j<=1;j++)
            S.mat[i][j]=(A.mat[i][j]+B.mat[i][j])%mod;
    return S;
}

matrix power(ll x)
{
    matrix S=E;
    int i=0;
    while(x)
    {
        if (x&1) S=S*M[i];
        x>>=1,i++;
    }
    return S;
}

matrix calc(ll k)
{
    if (!k) return Zero;
    if (k%2==0)
    {
        matrix S=calc(k>>1);
        return S+(power(k>>1)*S);
    }
    else return calc(k-1)+power(k);
}

int main()
{
    scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;i++)
    {
        ll x;
        scanf("%lld",&x);
        if (x>mx1)
        {
            mx2=mx1;
            mx1=x;
        }
        else if (x>mx2) mx2=x;
        sum=((sum+x)%mod+mod)%mod;
    }

    memset(Zero.mat,0,sizeof(Zero.mat));
    E.mat[0][0]=E.mat[1][1]=1;
    E.mat[0][1]=E.mat[1][0]=0;
    M[0].mat[0][0]=M[0].mat[0][1]=M[0].mat[1][0]=1;
    M[0].mat[1][1]=0;
    for(int i=1;i<=32;i++) M[i]=M[i-1]*M[i-1];

    while(mx2<0&&k)
    {
        mx2=(mx1+mx2)%mod;
        sum=(sum+mx2)%mod;
        k--;
    }
    Ans=calc(k);
    printf("%lld",(sum+Ans.mat[0][0]*mx1%mod+Ans.mat[0][1]*mx2%mod)%mod);

    return 0;
}
posted @ 2018-04-09 15:11  Maxwei_wzj  阅读(92)  评论(0编辑  收藏  举报