ACM比赛

【问题描述】  
  
  有三个小伙伴组队去参加 ACM 比赛,他们的比赛策略是这样的:每个队员都会对题目通看一遍,然后对每个题的难度进行估算,难度范围为1~5。当然,由于每个队员的水平和特点,他们对同一道题的估算不一定相同。接下来他们会对所有题目进行分配。三个人分配的题目刚好是所有题目,且不会有交集,而且每个人分配的题目的编号必须是连续的,每人至少要分一道题。请问,如何分配题目可以使得三个人拿到的题目的难度之和最小。每个人对自己分配到的题目只按自己的估算值求和。 
 
    
 【输入格式】  
  
  第一行一个数n,表示题目的数量。(3<=n<=150000) 
  接下来有3行。第i+1行有n个数,第j个数表示第i个人对于第j题的估算难度,难度介于1~5。
 
    
 【输出格式】  
   
  一个整数。表示最小的估算难度之和。 
 
    
 【输入样例】   
   
3
1 3 3 
1 1 1 
1 2 3 
 
    
 【输出样例】  
   

 
    
 【数据范围】  
   

1<=n<150000

【来源】  
  
重庆市NOIP2015模拟赛round1备用题


国庆节6号下午考的T2,结果是一个备用(bei tai)题......噗~

考试的时候一看题目直接懵了,smg啊,为什么不是两个人呢,三个人枚举分隔做题的位置O(N^2)必然超时GG啊,不知道当时怎么想的,太自信的乱设了一个状态,乱写了个方程(觉得自己是对的)交了上去,直接GG了,保底30(听说其实是50)都没有拿到,果然像我这样的蒟蒻实在应该先写个暴力啊~

回到正题上来,设clac(x,y,z)为第x个人做最前面的题,第y个人做中间的题,第z个人做后面的题的最小难度和.

最终答案Ans={clac(1,2,3) ,clac(1,3,2), clac(2,1,3), clac(2,3,1), clac(3,1,2), clac(3,2,1)}

计算clac的时候,设x做编号为1...i的题目,y做i+1...j的题目,z做j+1...n

这里可以暴力枚举i,j,不说了,肯定要超时。

正解的做法是再使用一个f数组,f[i]表示让x,y做前i道题的最小难度和,然后后面计算的时候只用枚举j就行了,这里再设x做前k道题,y就做k+1...i

  显然f[i]=min{sum[x][k]+sum[y][i]-sum[y][k] | 1<=k<i }=min{sum[x][k]-sum[y][k] | 1<=k<i }+sum[y][i];而用一个变量维护min{sum[x][k]-sum[y][k] | 1<=k<i }就可以让f[i]的计算变成O(n)

贴上代码(这几次test总感觉自己NOIP会GG)

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<cctype>
#define oo 2000000000
using namespace std;
const int maxn = 150005;
int n;
int a[4][maxn],sum[4][maxn];
int f[maxn];//f[i]表示让x,y做前i道题的最小难度和

//f(i)=min{sum[x][j]+sum[y][i]-sum[y][j] | 1<=j<i};
//数学变换得 
//f(i)=min{sum[x][j]-sum[y][j] | 1<=j<i}+sum[y][i];
//设minv=min{sum[x][j]-sum[y][j] | 1<=j<i}
//f(i)=minv+sum[y][i];

//clac(x,y,z)=min{f(i)+sum[z][n]-sum[z][i] | 2<=i<n} 
void _read(int &x)
{
	x=0;
	char ch;
	ch=getchar();
	while(ch>'9' || ch<'0')ch=getchar();
	
	while(ch>='0' && ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
}

void ready()
{
	memset(sum,0,sizeof(sum));
	
	for(int i=1;i<=3;i++)
	for(int j=1;j<=n;j++)
	sum[i][j]=sum[i][j-1]+a[i][j];
}

int clac(int x,int y,int z)
{
	//for(int i=1;i<=n;i++)f[i]=oo;
	
	f[2]=sum[x][1]+sum[y][2]-sum[y][1];
	int minv=min(sum[x][1]-sum[y][1],sum[x][2]-sum[y][2]);
	
	for(int i=3;i<n;i++)
	{
		f[i]=minv+sum[y][i];
		minv=min(sum[x][i]-sum[y][i],minv);
	}
	
	int ans=oo;
	for(int i=2;i<n;i++)
	{
		ans=min(ans,f[i]+sum[z][n]-sum[z][i]);
	}
	
	return ans;
}


int main()
{
	//freopen("acm.in","r",stdin);
	//freopen("acm.out","w",stdout);
	
	scanf("%d",&n);
	for(int i=1;i<=3;i++)
	for(int j=1;j<=n;j++)
	{
		_read(a[i][j]);
	}
	
	ready();
	
	int Ans=oo;
	Ans=clac(1,2,3);
	Ans=min(Ans,clac(1,3,2));
	Ans=min(Ans,clac(2,1,3));
	Ans=min(Ans,clac(2,3,1));
	Ans=min(Ans,clac(3,1,2));
	Ans=min(Ans,clac(3,2,1));
	
	printf("%d",Ans);
	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值