2020.01.21日常总结

C F 808 D       A r r a y    D i v i s i o n \color{green}{CF808D\ \ \ \ \ Array\ \ Division}

\color{blue}{【题意】:} 给你一组数(个数为 n n ),问是否可以最多移动一个数,使得这一串数可以分成两个部分,每一部分所有数的和相等。如果可以,则输出YES,否则请输出NO。(翻译摘自洛谷)

\color{blue}{【思路】:} 首先,最基本的,如果这组数的总和(记为 s u m sum )是奇数,那么肯定输出NO

在接着,我们可以考虑暴力,即枚举把哪个数移动到哪个位置上,但是会超时。

我们自然而然可以想到这样的算法:从前往后枚举 i i ,统计 j = 1 i a j \sum^{i}_{j=1} a_j (我们记为 a n s ans ),如果 s u m 2 a n s \frac{sum}{2}-ans 在原数组出现过,那么就可以达到题目要求。

#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e5+100;
typedef long long ll;
int a[N],n,b[N];ll ans,sum;
bool binary_search(int p){
	int l=1,r=n,mid;
	while (l<=r){
		mid=(l+r)>>1;
		if (b[mid]==p)
			return true;//找到数字p了,返回 
		else if (b[mid]>p)
			r=mid-1;
		else l=mid+1;
	}
	return false;//找到这了,证明没有这个数字 
}
//函数功能:查找b(排序后的a数组)中是否有数字p
int main(){
	n=read();ans=sum=0;
	for(int i=1;i<=n;i++){
		a[i]=b[i]=read();
		sum=sum+a[i];
	}
	if (sum%2){
		printf("NO");
		return 0;
	}
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++){
		ans+=a[i];
		if (binary_search(sum/2-ans)){
			printf("YES");
			return 0;
		}
	}
	printf("NO");
	return 0;
} 

我们发现这样做是错的 (惨痛的教训),原因有两个:一是我们只考虑了把后面的数字往前移动,其实际上,后面的数字也可以往前移动。解决方案是在加一重枚举,从后往前考虑。二是如果我们需要的数字只有一个,而且在 [ 1 , i ] [1,i] 之中,我们就会把它当作两个。解决方案是只在 [ i + 1 , n ] [i+1,n] 中查找它是否存在。

#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e5+100;
typedef long long ll;
int a[N],n,b[N];ll ans,sum;
bool find(ll p,int begin,int end){
	if (p==0) return true;
	int l=begin,r=end,mid;
	while (l<=r){
		mid=(l+r)>>1;
		if (b[mid]==p)
			return true;//找到数字p了,返回 
		else if (b[mid]>p)
			r=mid-1;
		else l=mid+1;
	}
	return false;//找到这了,证明没有这个数字 
}
//函数功能:查找b(排序后的a数组)从begin到end中是否有数字p
int main(){
	n=read();ans=sum=0;
	for(int i=1;i<=n;i++){
		a[i]=b[i]=read();
		sum=sum+a[i];
	}
	if (sum%2){
		printf("NO");
		return 0;
	}
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++){
		ans+=a[i];if (ans>sum/2) break;
		if (find(sum/2-ans,i+1,n)){
			printf("YES");
			return 0;
		}
	}
	ans=0;
	for(int i=n;i;i--){
		ans+=a[i];if (ans>sum/2) break;
		if (find(sum/2-ans,1,i-1)){
			printf("YES");
			return 0;
		}
	}
	printf("NO");
	return 0;
} 

虽然这样做看上去是完美无瑕的,然而我们发现这样还是错的 (又是惨痛的教训)。为什么呢(请读者稍作停留,思考一下,这对提升你的思维能力很有帮助!)?

答案是这样的。我们把 a a 数组排序后得到了 b b 数组,但是由于排序会 \color{red}{破坏原数组前后数字间的相对位置} 。所以 b b 数组中的第 1 1 i 1 i-1 个数也好,第 i + 1 i+1 到第 n n 个数也罢,都不一定是 a a 数组中的原数,也就是说,我们依然没有解决如上所述的问题!!!

解决方法有两个:一是每次把 a a 数组中需要查找的数放入 b b 数组中,但是这样做可能会导致超时。二是运用前缀和算法。把 b b 数组改为 a a 数组的前缀和数组。在第一重循环中,我们查找 b [ i + 1.. n ] b[i+1..n] 中是否有 b [ n ] 2 + a [ i ] \frac{b[n]}{2}+a[i] b [ n ] b[n] 为总和),如果有,则把 a [ i ] a[i] 移动到这个位置之后,正好 O K OK ;第二重循环类似,只是查找 b [ n ] 2 a [ i ] \frac{b[n]}{2}-a[i] ,请读者思考为什么。

#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e5+100;
typedef long long ll;
int a[N],n;ll b[N];
bool find(ll p,int begin,int end){
	int l=begin,r=end,mid;
	while (l<=r){
		mid=(l+r)>>1;
		if (b[mid]==p)
			return true;//找到数字p了,返回 
		else if (b[mid]>p)
			r=mid-1;
		else l=mid+1;
	}
	return false;//找到这了,证明没有数字p 
}
//函数功能:查找b(a数组的前缀和数组)从begin到end中是否有数字p
int main(){
	freopen("t1.in","r",stdin);
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		b[i]=b[i-1]+a[i];
	}
//因为a数组内的数都是正整数,所以b数组天然有序 
	if (b[n]%2){//总和是奇数,一定不能分为两个相等的部分 
		printf("NO");
		return 0;
	}
	for(int i=1;i<=n;i++)
		if (find(b[n]/2+a[i],i+1,n)){
			printf("YES");
			return 0;
		}
	for(int i=n;i;i--)
		if (find(b[n]/2-a[i],1,i-1)){
			printf("YES");
			return 0;
		}
	printf("NO");
	return 0;
} 

终于 A C AC 了。

发布了82 篇原创文章 · 获赞 4 · 访问量 1759

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/104061040
今日推荐