NOIP2018龙虎斗

原题大意

题目描述

一种很抽象的游戏。

轩轩和凯凯正在玩一款叫《龙虎斗》的游戏,游戏的棋盘是一条线段,线段上有 n 个兵营(自左至右编号 1∼n),相邻编号的兵营之间相隔 1 厘米,即棋盘为长度为 n−1 厘米的线段。i 号兵营里有 c[i] 位工兵。 下面图 1 为 n=6 的示例:

轩轩在左侧,代表“龙”;凯凯在右侧,代表“虎”。 他们以 m 号兵营作为分界, 靠左的工兵属于龙势力,靠右的工兵属于虎势力,而第 m 号兵营中的工兵很纠结,他们不属于任何一方。

一个兵营的气势为:该兵营中的工兵数 ╳ 该兵营到 m 号兵营的距离;参与游戏 一方的势力定义为:属于这一方所有兵营的气势之和。
下面图 2 为 n=6,m=4 的示例,其中红色为龙方,黄色为虎方:

游戏过程中,某一刻天降神兵,共有 s1​ 位工兵突然出现在了 p1​ 号兵营。作为轩轩和凯凯的朋友,你知道如果龙虎双方气势差距太悬殊,轩轩和凯凯就不愿意继续玩下去了。为了让游戏继续,你需要选择一个兵营 p2​,并将你手里的 s2​ 位工兵全部派往 p2​,使得双方气势差距尽可能小。

注意:你手中的工兵落在哪个兵营,就和该兵营中其他工兵有相同的势力归属(如果落在 m 号兵营,则不属于任何势力)。

输入格式

输入文件的第一行包含一个正整数 n,代表兵营的数量。

接下来的一行包含 n 个正整数,相邻两数之间以一个空格分隔,第 i 个正整数代 表编号为 i 的兵营中起始时的工兵数量 c[i] 。

接下来的一行包含四个正整数,相邻两数间以一个空格分隔,分别代表 m,p1​,s1​,s2​。

输出格式

输出文件有一行,包含一个正整数,即 p2​,表示你选择的兵营编号。如果存在多个编号同时满足最优,取最小的编号。

输入输出样例

输入样例1                         输出样例1
      
6                                 2
2  3  2  3  2  3
4  6  5  2 
|  |  |  |
m  p1 s1 s2

注:m是分界线,有s1个人突然出现在兵营p1,现要让s2个人出现在兵营p2,找出兵营p2。

一个兵营的气势为:该兵营中的工兵数 ╳ 该兵营到 m 号兵营的距离

双方以 m=4 号兵营分界,有 s1​=5 位工兵突然出现在 p1​=6 号兵营。 龙方的气势为:

2×(4−1)+3×(4−2)+2×(4−3)=14

虎方的气势为:

2×(5−4)+(3+5)×(6−4)=18

当你将手中的 s2​=2 位工兵派往 p2​=2 号兵营时,龙方的气势变为:

14+2×(4−2)=18

此时双方气势相等。

输入样例2                         输入样例2

6                                 1
1  1  1  1  1  16 
5  4  1  1
|  |  |  |
m  p1 s1 s2

注:m是分界线,有s1个人突然出现在兵营p1,现要让s2个人出现在兵营p2,找出兵营p2。

一个兵营的气势为:该兵营中的工兵数 ╳ 该兵营到 m 号兵营的距离

双方以 m=5 号兵营分界,有s1​=1 位工兵突然出现在 p1​=4 号兵营。
龙方的气势为:

1×(5−1)+1×(5−2)+1×(5−3)+(1+1)×(5−4)=11

虎方的气势为:

16×(6−5)=16

当你将手中的 s2​=1 位工兵派往 p2​=1 号兵营时,龙方的气势变为:

11+1×(5−1)=1511+1×(5−1)=15

此时可以使双方气势的差距最小。

数据规模与约定

1<m<n,1≤p1​≤n。
对于 20% 的数据,n=3,m=2,c[i]=1,s1​,s2​≤100。
另有 20% 的数据,n≤10,p1​=m,c[i]=1,s1​,s2​≤100。
对于 60% 的数据,n≤100,c[i]=1,s1​,s2​≤100。
对于 80% 的数据,n≤100,c[i],s1​,s2​≤100。
对于 100% 的数据,n≤10⁵,c[i]​,s1​,s2​≤10⁹。

---------------------------------------------------------------------------------------------------------------------------------

本题解法

因为本题n最大为10⁵,而龙虎斗是线性的,可以用一维数组存储,故选择采用暴力枚举,时间复杂度是O(n),绰绰有余。

60分解法

不难发现,前60分 c[i] 都为1,n也比较小,可以先入手。

我们可以先计算出龙方和虎方分别的势气总和,然后比较大小,确定是为哪一方招兵买马(并枚举出在哪一兵营加士兵能使两方势气差值最小),或者谁也不帮(即当龙方势气等于虎方势气时,p2等于m)。

因为 c[i] 都等于 1,所以循环时不用再乘上当前兵营的人数了。

所以,代码如下:

#include<bits/stdc++.h>
using namespace std;
int read(){//快读
	int dat=0,t=1;
	char ch;
	ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')t=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		dat=(dat<<1)+(dat<<3)+(ch&15);
		ch=getchar();
	}
	return dat*t;
}
int a[100004],b[100004];//a数组记录当前兵营的人数,b数组记录当前兵营距离分界线的距离
int n,i,m,s1,p1,s2,p2;
int sum_long,sum_hu;//sum_long表示龙方势气总和,sum_hu表示虎方势气总和
int min1=1000000001;//用min1来记录两方势气总和的最小差值
int main(){
	n=read();
	for(i=1;i<=n;i++){//输入每个兵营人数
		a[i]=read();
	}
	m=read();
	for(i=1;i<=n;i++){//预处理b数组
		b[i]=abs(m-i);
	}
	p1=read();
	s1=read();
	s2=read();
	for(i=1;i<m;i++){//求龙方势气总和
		sum_long+=b[i];
	}
	for(i=m+1;i<=n;i++){//求虎方势气总和
		sum_hu+=b[i];
	}
	if(p1<m){//加上新来的士兵势气
		sum_long+=(m-p1)*s1;
	}
	if(p1>m){
		sum_hu+=(p1-m)*s1;
	}
	if(sum_long>sum_hu){//虎方势气小,在虎方加士兵
		for(i=m+1;i<=n;i++){//枚举p2
			if(abs(sum_hu+s2*b[i]-sum_long)<min1){//比较在此兵营加士兵是否使两方差值目前最小
				min1=abs(sum_hu+s2*b[i]-sum_long);//打擂台
				p2=i;
			}
		}
	}
	if(sum_long<sum_hu){//龙方势气小,在龙方加士兵
		for(i=1;i<m;i++){
			if(abs(sum_long+s2*b[i]-sum_hu)<min1){
				min1=abs(sum_long+s2*b[i]-sum_hu);
				p2=i;
			}
		}
	}
	if(sum_long==sum_hu){//两方势气相等,在分界线加士兵
		p2=m;
	}
	printf("%d",p2);//输出p2
	return 0;
}

正解

思路和60分相似,但是我们又多了一些细节处理。

每次枚举还要考虑人数。

当然,我们可以直接把新来的s1为士兵加到兵营p1里去,假装他们本来就在p1兵营里,方便计算。

但是n最大为10⁵,c[i] 最大为10⁹,10⁵ * 10⁹ 显然超出了int范围,所以不开long long 见祖宗!

#include<bits/stdc++.h>
using namespace std;
long long read(){//快读
	long long dat=0,t=1;
	char ch;
	ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')t=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		dat=(dat<<1)+(dat<<3)+(ch&15);
		ch=getchar();
	}
	return dat*t;
}
long long a[100004],b[100004];//a数组记录当前兵营的人数,b数组记录当前兵营的势气 
long long l,r;//l表示龙方势气总和,r表示虎方势气总和
long long min1;//用min1来记录两方势气总和的最小差值
long long n,i,m,s1,s2,p1,p2,k;//不开long long 见祖宗!
int main(){
	n=read();
	for(i=1;i<=n;i++){//输入每个兵营人数
		a[i]=read();
	}
	m=read();
	p1=read();
	s1=read();
	s2=read();
	a[p1]+=s1;//加入新来的兵 
	p2=m;
	for(i=1;i<=n;i++){
		b[i]=abs(m-i)*a[i];//计算出每个兵营的势气 
		if(i<m)l+=b[i];//求龙方势气总和 
		else r+=b[i]; //求虎方势气总和 
 	}
 	min1=abs(l-r);
 	if(l<r){//龙方势气小,在龙方加士兵
		for(i=1;i<=m;i++){//枚举p2
			k=s2*(abs(m-i));//计算在此处加士兵增加的势气 
			if((abs(l+k-r))<min1){//比较在此兵营加士兵是否使两方差值目前最小
				min1=abs(l+k-r);//打擂台
				p2=i;
			}
		}	
	}else if(r<l){//虎方势气小,在虎方加士兵
		for(i=m+1;i<=n;i++){
			k=s2*(abs(m-i));
			if((abs(l-r-k))<min1){
				min1=abs(l-r-k);
				p2=i;
			}
		}		
	}
	printf("%lld",p2);//输出p2 
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值