ACM (线性+前缀和+后缀和)

​题目描述:2015 年 UOI 马上就要开始了,CSSYZ2015 信息队派出最强三位同学组成“宇宙队”参加这次 ACM 比赛,周老师想让他们以最优的策略去完成比赛,于是,将题目分为 5 等,编号 1 到 5,数字越大,难度越大。对于每一个同学,同一道题可能难度不一样,现在,周老师想知道,如何安排学生做题顺序,能使难度值总和最低。注意:每位同学只能做编号连续的题目,并且不能一道题不做。

输入
第一行一个整数 N(3<=N<=150000),为题目数量。
第二行到第四行,每行 N 个整数,代表每个题目对于三名同学的难度。

输出
一行,最小难度总和。

样例输入1
3
1 3 3
1 1 1
1 2 3

样例输入2
7
3 3 4 1 3 4 4
4 2 5 1 5 5 4
5 5 1 3 4 4 4

样例输出1
4
样例输出2
19

题目思路

  1. 首先大家都能够想到这道题能转化。因为每个人至少做一道题,且做的题目编号要连续 (这个编号不是难度编号,是输入顺序的编号。别问我怎么知道的,我是不会告诉你我因这个细节WA了无数次的)。所以,我们就能知道有一个人做了从第一个题开始的若干个题,还有一个人做了以最后一道题结尾的若干个题,剩下的就是第三个人做了。

画张图形象的描述一下。

//图

所以我们发现,题目被i和j(i+1<j)两个点分成了三部分,这道题就被巧妙地转化成了:先将三个人全排列,确定哪个人来做那一块连续的题;对于每一种排序,找到所有i和j,使其满足i+1<j,然后求难度和,然后取 min,得出最小难度总和。

  1. 接下来就可以暴搜了!!! 望了一眼数据,本以为已经取得胜利的我,原来还在起跑线上。

既然暴力不行,就试试其他办法,再仔细看看我们目前的线索,有一个点十分引人注目:有一个人做了从第一个题开始的若干个题,还有一个人做了以最后一道题结尾的若干个题。

左青龙,右白虎,前朱雀,后玄武,中间一个250.

呸!呸!一个在前,一个在后。可以用前缀和与后缀和来优化呀!

  1. 然后我们就可以惊奇的发现,没有用。

但是我们得到一个结论:我们需要从两边进行考虑。这时,我们就可以假设左边第一块题为0,右边第一块也为0,也就是中间那个人需要写所有的题目,但是其他人也要写题啊,所以就可以开始扩展。

Ans(难度和) a[ i ] (对于第1个人来说第i道题的难度)
b [ i ] (对于第2个人来说第i道题的难度)
c [ i ] (对于第3个人来说第i道题的难度)

那么,未扩展前,Ans=b[1]+b[2]+…+b[n];

如果要将前i道题给第一个人做,那么第1 ~ i道题的难度就由b[1 ~ i]变为a[1~i]。

所以可得出Ans=b[1]+b[2]+…+b[n]+(a[1]-b[1])+(a[2]-b[2])+…+(a[i]-b[i]);

同理,如果要将后j道题给第三个人做,那么第j~n道题的难度就由b[j~n]变为了c[j~n];

所以在上述的情况下可得出结论Ans=b[1]+b[2]+…+b[n]+(a[1]-b[1])+(a[2]-b[2])+…+(a[i]-b[i])+(c[j]-b[j])+(c[j+1]-b[j+1])+…+(c[n]-b[n]);

我们再用用前缀和与后缀和优化一下,定义两个数组。

f [i] (a[1~i]-b[1~i]的和) g [j] (c[j~n]-b[j~n]的和)

最终,Ans=b[1~n]+(f [i]+g [j]);(i+1<j,因为第二个人至少得做一道题)

也就是说,我们要使Ans最小,就要让b[1~n]+(f [i]+g [j])最小,但是b[1~n]是一直不会改变的,所以只要使f [i]+g [j]最小就行了。

  1. 然后,我们就又能够惊奇的发现,这还不是找i和j吗?不还是O(n*n)的复杂度吗?

别着急,我们可以只枚举j,而i<j-1的,要使f [i]+g [j]最小,因为g [j]是固定的,所以我们就可以让f [i]最小就行了,也就是f [1~(j-2)]中最小的那个;这样,我们先求出f [1~n],然后定义另一个数组x [i]表示为在f [1~i]中最小的。

这样,枚举j,Ans=min(Ans, b[1~n]+(x[j-2]+g[j]));

  1. 这样就可以简简单单 地A掉这道题了。

代码如下:

#include<bits/stdc++.h>
#define R register int 
using namespace std;

int ans=1000000007, n;
int pos[4][150005];
int f[150005];
int g[150005];
int m[4];

void work(int a, int b, int c){
    for(R j=1;j<=n;++j)	f[j]=f[j-1]+pos[a][j]-pos[b][j];
    for(R j=n;j>=1;--j)	g[j]=g[j+1]+pos[c][j]-pos[b][j];
    for(R j=2;j<=n;++j)	f[j]=min(f[j], f[j-1]);
	
    for(R i=n;i>=3;--i)
	ans=min(ans, m[b]+f[i-2]+g[i]);
}
int main (){
    freopen("acm.in", "r", stdin);
    freopen("acm.out", "w", stdout);
    scanf ("%d", &n);
    for(R i=1;i<=3;++i)
    	for(R j=1;j<=n;++j){
	    scanf ("%d", &pos[i][j]);
		m[i]+=pos[i][j];
	}
    work(1, 2, 3);
    work(1, 3, 2);
    work(2, 1, 3);
    work(2, 3, 1);
    work(3, 1, 2);
    work(3, 2, 1);
    printf("%d\n", ans);
	
    return 0;
}

谢谢观看,蒟蒻有不对的地方,还请多多指教。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值