CCPC2022桂林 C. Array Concatenation(gym 104008C)

23 篇文章 0 订阅
10 篇文章 0 订阅

Problem - C - Codeforces

题目大意:有一个长度为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;
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timidcatt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值