题目大意:有一个长度为n的数组和规定的m次操作数,有两种操作,操作一是将当前的数组复制到数组的末尾,操作二是将数组倒置后放到数组的前面,问操作m次后最大的前缀和的和是多少,要求求出对1e9+7求余后的余数的最大
1<=n,m<=1e5;1<=ai<=1e9
思路:对于初始序列的前缀和的和,可以用n*a1+(n-1)*a2+...+1*an求出,进行一次操作一后的数组的前缀和的和为2n*a1+(2n-1)*a2+...+(n+1)*an+n*a1+(n-1)*a2+...+1*an,我们发现这个式子的后一部分和原本的数组的前缀和的和是相同的,前一部分也有一部分相同,而不同的部分正好是n倍的前缀和,再考察进行两次操作一的数组,我们发现其前缀和的和为6n个前缀和+4个初始序列的前缀和的和,所以我们可以将其抽象成如下这个图:
其中sum为原始数组的前缀和的和,sum2为n倍原始数组的前缀和,下图为初始数组进行3次操作1后的情况,边长为2^操作次数
我们发现sum2的数量为等差数列的和,于是我们可以得到进行了i次操作1的数组的前缀和的和为(2^i-1)*2^(i-1)*sum2+2^i*sum;
然后我们发现在执行一次操作2后数组就变成了一个回文数组,然后我们无论再选择哪个操作都相当于操作1,因为我们上面已经知道了求前缀和的和只要O1,所以我们可以枚举在第1到第m次操作中哪一次进行操作2,或者不进行操作2,在第i次操作进行一次操作2后,新的部分构成的三角形中那一列下面的sum2不变,上面的sum变成原始数组倒置后的数组的前缀和的和sumre即1*a1+2*a2+...+n*an,然后将进行操作2之后的数组当做原始数组,更新sum=2^i-1*sum+2^(i-1)*(2^i-1)*sum2+2^(i-1)*sumre;sum2的值更新为2^i*2^i*sum2,再进行m-i次操作1,就能得出操作m次的结果,我们在枚举i时维护最大值即可
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
ll qpow(ll a, ll b)
{//快速幂
if (b <= 0)
return 1;
ll ret = 1,temp=a;
while (b)
{
if (b & 1)
{
ret = ret * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return ret % MOD;
}
int main()
{
ll n, m;
cin >> n >> m;
ll sum = 0;
ll sumre = 0;
ll sum2 = 0;
for (ll i = 1; i <= n; i++)
{
ll x;
scanf("%lld", &x);
sum = (sum + ((n - i + 1) * x % MOD)) % MOD;//求前缀和的和
sum2 = (sum2 +( n * x % MOD)) % MOD;//求n倍的前缀和
sumre = (sumre + (i * x % MOD)) % MOD;//求倒置数组的前缀和的和
}
ll ans = (((((qpow(2, m) - 1) * qpow(2, m - 1)) % MOD * sum2) % MOD) + sum * qpow(2, m) % MOD) % MOD;//一次操作2都不进行的答案
for (ll i = 1; i <= m; i++)
{//枚举第i次进行操作2
ll pow1 = qpow(2, i), pow2 = qpow(2, i - 1);
ll temp1 = ((pow2*sum)%MOD + ((pow2*(pow1-1)) % MOD * sum2) % MOD + pow2 * sumre % MOD) % MOD;//进行操作2后的sum
ll sum3 = ((pow1 * pow1) % MOD * sum2) % MOD;//进行操作2后的sum2
ll temp2 = temp1;
if (i != m)
temp2 = (((((qpow(2, m - i) - 1) * qpow(2, m - i-1)) % MOD * sum3) % MOD) + temp1 * qpow(2, m - i) % MOD) % MOD;//继续进行(m-i)次操作1
ans = max(ans, temp2);//维护最大值
}
cout << ans;
return 0;
}
思路2:我们从上述思路中也可以发现,无论是第几次操作进行了操作2,最后一定有一半的三角形是sumre,一半是sum,下面的小三角形sum2是一样的,所以我们可以直接算出有一半倒置,一半正序和全部都是正序两种情况的答案,最后取最大值即可
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
ll qpow(ll a, ll b)
{
if (b <= 0)
return 1;
ll ret = 1;
while (b)
{
if (b & 1)
{
ret = ret * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return ret % MOD;
}
int main()
{
ll n, m;
cin >> n >> m;
ll sum = 0;
ll sumre = 0;
ll sum2 = 0;
for (ll i = 1; i <= n; i++)
{
ll x;
scanf("%lld", &x);
sum = (sum + ((n - i + 1) * x % MOD)) % MOD;
sum2 = (sum2 + (n * x % MOD)) % MOD;
sumre = (sumre + (i * x % MOD)) % MOD;
}
ll pow1 = qpow(2, m - 1), pow2 = qpow(2, m);
ll ans1 = (((pow2 - 1) * pow1) % MOD * sum2) % MOD;//所有正方形的和
ll ans2 = (pow1 * (sumre + sum)%MOD) % MOD;//一半倒置,一半正序
ll ans3 = (sum * pow2) % MOD;//都是正序
ll ans = (ans3 + ans1) % MOD;
ll anss = (ans2 + ans1) % MOD;//分开求再比大小
cout << max(ans, anss);
return 0;
}