[BZOJ4547]小奇的集合(矩乘)

本文针对一种特定算法竞赛题目提供了解决方案,通过分析序列中非负数的操作规律,利用斐波那契数列和矩阵快速幂的方法高效求解。文章详细介绍了如何根据不同情况采取最优策略,并给出了完整的代码实现。

题目描述

传送门

题解

由于数据保证最后的答案为非负数,那么序列就有两种情况:
1、序列中有两个或两个以上非负数。
2、序列中只有一个非负数。
对于第一种情况,每一次的操作一定是将最大的两个数相加放入序列中。比如有不降序列: a1,a2,a3,an1,an ,进行多次操作就可以写出如下的序列:
a1,a2,a3an1,an,an1+an
a1,a2,a3an1,an,an1+an,an1+2an
a1,a2,a3an1,an,an1+an,an1+2an,2an1+3an
a1,a2,a3an1,an,an1+an,an1+2an,2an1+3an,3an1+5an
...
可以看出 an1 an 的系数呈斐波那契数列。那么我们就需要求斐波那契数列的前缀和,这个 O(k) 的递推可以用矩阵快速幂来加速。

[sn1fnfn1]110011010=[snfn+1fn]

对于第二种情况,一定是唯一的一个非负数数和数列中的最大的负数相加,直到数列中有两个非负数,再进行第一种操作。
我的方法比较麻烦,用等差数列的求和公式求出负数和非负数相加过程中的和。实际上由于 |ai|<=100000 ,可以直接暴力计算。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long
#define N 100005
#define Mod 10000007

int n,p;
struct hp{LL a[5][5];}A,unit,mi,ans;
LL a[N],sum,s1,s2,k1,k2;

inline hp cheng(hp a,hp b)
{
    hp ans;
    memset(ans.a,0,sizeof(ans.a));
    for (int i=1;i<=3;++i)
        for (int j=1;j<=3;++j)
            for (int k=1;k<=3;++k)
                ans.a[i][j]=(ans.a[i][j]+a.a[i][k]*b.a[k][j]%Mod)%Mod;
    return ans;
}
inline hp fast_pow(hp a,int p)
{
    hp ans=unit;
    for (;p;p>>=1,a=cheng(a,a))
        if (p&1)
            ans=cheng(ans,a);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&p);
    for (int i=1;i<=n;++i) scanf("%lld",&a[i]),sum=(sum+a[i])%Mod;
    sort(a+1,a+n+1);

    if (a[n-1]<0)
    {
        int t;
        if ((-a[n-1])%a[n]==0) t=(int)(-a[n-1])/a[n];
        else t=(int)((-a[n-1])/a[n])+1;
        p-=t;
        a[n+1]=t*a[n]+a[n-1];
        s1=min(a[n],a[n+1]),s2=max(a[n],a[n+1]);
        LL nn=(t-1)*t/2;
        nn%=Mod;
        sum=(sum+(t*a[n+1]%Mod-a[n]*nn%Mod)%Mod)%Mod;
    }
    else
    {
        s1=a[n-1]; s2=a[n];
    }
    if (p==0)
    {
        printf("%lld\n",sum);
        return 0;
    }
    for (int i=1;i<=3;++i)
        for (int j=1;j<=3;++j) unit.a[i][j]=ans.a[i][j]=0;
    for (int i=1;i<=3;++i) unit.a[i][i]=1;
    A.a[1][1]=A.a[2][1]=A.a[2][2]=A.a[2][3]=A.a[3][2]=1;
    mi=fast_pow(A,p-1);
    ans.a[1][1]=ans.a[1][2]=ans.a[1][3]=1;
    ans=cheng(ans,mi);
    k1=ans.a[1][1]; k2=(ans.a[1][1]-1+ans.a[1][2])%Mod;
    sum=(sum+(s1*k1%Mod+s2*k2%Mod)%Mod)%Mod;
    sum=((sum%Mod)+Mod)%Mod;
    printf("%lld\n",sum);
}

总结

1、考虑问题情况要全面。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值