CodeForces-1016C Vasya And The Mushrooms(模拟+思维+前缀和的前缀和) 解题报告 Apare_xzc

CodeForces-1016C Vasya And The Mushrooms(模拟+思维+二重前缀和 ) 解题报告

xzc 2019/4/7

这周周赛的C题:wyt学姐的恶意
  这道题周赛的时候就出思路了,但是觉得可能很不好写,20分钟写了个大概,处理了前缀后缀和以及二重前缀后缀和,没推完公式


vjudge链接
codeforces链接

题意:

  有一个蘑菇园,是一个2*n的方格地。每个格子里都有一颗蘑菇,蘑菇的生长速度不同。
  Vasya这个人一开始站在左上角那个格子里,他必须走到每个格子里去采蘑菇,并且不能停留,1秒移动到一个相邻的格子,并且迅速采了这个格子的蘑菇(时间忽略不计),然后马上离开。而且他必须经过每个格子1次
  一开始时间为蘑菇高度都为0。求这个人能采到蘑菇高度之和的最大值是多少。(左上角那株肯定是0)


输入:

  n
  a1 a2 … an(代表第一行每列蘑菇每秒生长的高度)
  b1 b2 … bn(代表第二行每列蘑菇每秒生长的高度)


输出:

  一个整数,表示他能采到蘑菇高度之歌的最大值


Sample

 input1

3
1 2 3
6 5 4

 output1

70

 input2

3
1 1000 10000
10 100 100000

 output2

543210

input3(不用谢我)

6
12 8 12 17 20 5
17 4 8 8 8 4

output3

705

input4(不用谢我)

6
16 20 18 7 14 2
17 12 4 10 7 4

output4

741

分析:

(以样例4为例)

列数123456
a(生长速度)1620187142
b(生长速度)171241074

我们可以把每个位置用一个字母标号:

列数123456
aABCDEF
bGHIJKL
  • 那么,根据题意,这个人一开始在A这里,他必须采了高度为0的蘑菇,然后马上向相邻的格子转移,他只能向下走或者向右走。
  • 那么,根据题目的要求,他每个格子只能走一次,如果他第一步向右边走到了B,那么他的路径就确定了,只能是A->B->C->D->E->F->L->K->J->I->H->G
  • 如果他第一步往下走,到了G,那么他下一步只能往右从G到H
  • 现在他到了H,他又有2种选择:向右走到I(A->G->H->I),或者向上走到B(A->G->H->B)
  • 如果他选的是从H到I,那么他的路径又确定了,只能是A->G->H->I->J->K->L->F->E->D->C->B
  • 如果他选的是从H到B,那么他下一步只能往右从B到C
  • 现在他到了C,他又有两种选择:向右走到D(A->G->H->B->C->D),或者向下走到I(A->G->H->B->C->I)
  • 如果他选的是从C到D,那么他他的路径叒确定了,只能是A->G->H->B->C->D->E->F->L->K->J->I
  • 如果他选的是从C到I,那么下一步他只能往右到J,继续走蛇形…
  • (你懂我意思了吧)

所以,我们的思路就确定了,他能走的路线不多,少于2*n条,所以,我们可以用每条路线的答案ans去更新最终的结果res
我们可以这样写:
  循环的主线是让他走蛇形A->G->H->B->C->I->J->D->E->K->L->F
  在走这条主线的时候,在每个位置,如果该上下了他却往右走了,那么他的路径就确定了,我们计算一下他开小差走的其它路径的答案,更新一下结果,然后继续走蛇形
  那么问题来了,循环主线复杂度是O(2*n)的,我们能否O(1)或者O(logn)地求出图中每个点“开小差”后的结果呢?
我们就想到了前缀和


我们来模拟一下某一条路:
假如我们走蛇形,走到第4列J的位置,此时的时间t=7,我们本应该向上走到D,但是我们从J向右走到了K,那么后面的路就是(A->G->H->B->C->I->J)->K->L->F->E->D
那么我们如何计算从K到D这一段蘑菇的高度(设为h)呢?
我们可以暴力地算一遍:
h = 8*K + 9*L + 10*F + 11*E + 12*D
那么,我们可以先从J和L开始计时,把h表示为:
h = (1+7)*K + (2+7)*L + (1+9)*E + (2+9)*D

那么,7和9是怎么来的呢?
设J所在列数为i,则i = 4
7 = t
10 = 7+6-4 = t+n-i
那么h就可以表示为:

  t * (b[i]+b[i+1]+...+b[n])  -------------------->(1)
+ b[i]*1 + b[i+1]*2 + ... + b[n]*(n-i+1) --------->(2)    
+ (t+n-i) * (a[n]+a[n-1]+a[n-1]+...+a[i])--------->(3) 
+ a[n]*1 + a[n-1]*2 + ... + a[i]*(n+1-i) --------->(4)

那我们要如何求这4个式子呢?

  • 显然(1)(3)可以用数组a,b的前缀和(后缀和更好)
  • 那么(2)(4)怎么求呢?
  • 注意了!(2)不就是数组b后缀和的后缀和吗?
        (4)不就是数组a前缀和的前缀和吗?

我们可以预处理之后O(1)算出来(1)(2)(3)(4)

至此,这题可以做了,而且复杂度为O(n)
我们先定义几个数组
sumaL: a数组的前缀和
sumaR: a数组的后缀和
sal : a数组前缀和的前缀和
sar : a输入后缀和的后缀和
(b的同理)


上代码:

/*
Author: XuZhichao
Status: Accepted   Time: 124ms
Memory: 23512kB    Length: 1864
Lang: GNU G++11 5.1.0
Submitted: 2019-04-07 00:53:49
*/
#include <bits/stdc++.h>
#define Mst(a,b) memset(a,(b),sizeof(a))
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define LL long long
#define MP make_pair
#define pb push_back
using namespace std;
const int maxn = 3e5 + 100;
LL a[maxn],b[maxn],res; 
LL sumaL[maxn],sumaR[maxn],sumbL[maxn],sumbR[maxn];
LL sal[maxn],sar[maxn],sbl[maxn],sbr[maxn];
void downToRight(LL ans,int i,int n,LL t)
{
	ans += t*sumbR[i+1]+sbr[i+1]+(t+n-i)*sumaR[i]-sal[i-1]+sal[n]-sumaL[i-1]*(n-i+1);
	res = max(res,ans);
}
void upToRight(LL ans,int i,int n,LL t)
{
	ans += t*sumaR[i+1]+sar[i+1]+(t+n-i)*sumbR[i]-sbl[i-1]+sbl[n]-sumbL[i-1]*(n-i+1);
	res = max(res,ans);
}
int main()
{
	//freopen("in.txt","r",stdin);
	int n;
	int ca = 0;
	while(scanf("%d",&n)!=EOF)
	{
		if(ca++)
		{
			sumaL[0] = sumaL[n+1] = 0;
			sumbL[0] = sumbL[n+1] = 0;
			sumaR[0] = sumaR[n+1] = 0;
			sumbL[0] = sumbR[n+1] = 0;
			sal[0] = sal[n+1] = 0;
			sar[0] = sar[n+1] = 0;
			sbl[0] = sbl[n+1] = 0;
			sbr[0] = sbr[n+1] = 0;
		}
		For(i,1,n) scanf("%lld",a+i),sumaL[i] = sumaL[i-1]+a[i];
		For(i,1,n) scanf("%lld",b+i),sumbL[i] = sumbL[i-1]+b[i];
		if(n==1)
		{
			printf("%lld\n",b[1]);
			continue;
		}
		Rep(i,n,1) sumaR[i] = sumaR[i+1]+a[i],sumbR[i] = sumbR[i+1]+b[i];
		For(i,1,n) sal[i]  = sal[i-1]+sumaL[i], sbl[i] = sbl[i-1]+sumbL[i];
		Rep(i,n,0) sar[i]  = sar[i+1]+sumaR[i], sbr[i] = sbr[i+1]+sumbR[i];
		res = 0;
		res = sar[2]+sbl[n]+sumbL[n]*(n-1);
		LL t = 0;
		LL ans = 0;
		For(i,1,n)  
		{
			++t;
			if(i&1) ans += t*b[i];
			else ans += t*a[i];
			if(i==n) 
			{
				res = max(res,ans);
				continue;
			}
			++t;
			if(i&1)
			{
				ans += t*b[i+1];
				if(i<n-1)
					downToRight(ans,i+1,n,t); 
			}
			else
			{
				ans += t*a[i+1];
				if(i<n-1)
					upToRight(ans,i+1,n,t);
			}
		}
		printf("%lld\n",res);
	}
	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值