遇上一个强迫症人——怎么均分纸牌

本文介绍了如何帮助强迫症的阿瓜在有限时间内通过特定移动规则使得每堆纸牌数量均等。通过分析题目,建立数学模型,设置标记变量并确定移动策略,最终实现用最少移动次数达成目标。文章提供了完整的C++代码解决方案。
摘要由CSDN通过智能技术生成

题目来自:「NOIP 2002」均分纸牌

A.遇到一个问题

阿瓜是一个强迫症人……

有一天,邻居约他出来打牌,但是村子里 没有一副完整的扑克牌 。怎么办? 大家把各自的牌都拿了出来,没人知道这些牌有多少……

n个人围在一张桌子旁,洗牌的人把牌分成了n堆,编号分别为1,2,…,N。每堆上有纸牌数量不等。只是阿瓜见每堆牌不等,很是心烦!于是他决定使每堆上纸牌数都一样多。阿瓜突发奇想,制定下面的移动规则

在编号为1堆上取的纸牌,只能移到编号为2的堆上;

在编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;

其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

但是邻居们可不想让阿瓜耽搁太多时间,因此,阿瓜必须在一秒内算出答案!

请你帮帮可怜的阿瓜吧!

输入

N(N堆纸牌,1<=N<=100 A1 A2 … An(N堆纸牌,每堆纸牌初始数,l<=Ai< =10000)

输出

所有堆均达到相等时的最少移动次数。

样例输入

4
9 8 17 6

样例输出

3

如果你想读正版题目描述:

有N堆纸牌,编号分别为1,2,…,N。每堆上有若干张,但纸牌总数必为N的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为1堆上取的纸牌,只能移到编号为2的堆上;在编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。  
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

给个样例解释

例如 N=4,4 堆纸牌数分别为:① 9 ② 8 ③ 17 ④ 6   
移动3次可达到目的:

从  3  取  4  张牌放到  4  (9  8  13  10)  ->   

从  3  取  3  张牌放到  2  (9  11  10  10)->   

从  2  取  1  张牌放到  1  (10  10  10  10)。

B.开始建模

当我们能建立一个数学模型,离成功就不远了。

可是,很多新手,抄手甚至高手,老手见了题目都会不知所措,爱往复杂处想,其实这也是每年CCF套路之一😁😁😁,但是呢大家如果仔细读,这是一道~~很简单~~的的题目,下面呢我们来分析一下错误的模型构造做题方法。

温馨提示:如果您有点不耐烦,可以跳过。

读题不认真

我才不会告诉你,这道题 **我也没认真读 **。

Maybe有朋友会如此读题:

n个人围在一张桌子旁,洗牌的人把牌分成了n堆,编号分别为1,2,…,N。每堆上有纸牌数量不等。只是阿瓜见每堆牌不等,很是心烦!于是他决定使每堆上纸牌数都一样多。阿瓜突发奇想,制定下面的移动规则

在编号为1堆上取的纸牌,只能移到编号为2的堆上;

在编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;

其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

但是邻居们可不想让阿瓜耽搁太多时间,因此,阿瓜必须在一秒内算出答案!

请你帮帮可怜的阿瓜吧!

哈哈,我一开始正是如此

于是我会想:

So easy ,我只需要将它们排个序,每次用最大的和最小的互相填充,再算……

紧接着你会写出以下代码:

#include<aldorithm>
#include<cstdio>
#include<iostream>

using namespace std;

int n,run,cnt,a[101];

int bracsort(){
	int l=1,r=n;
    
	while(l<r){
		if(a[l]==a[r]){
			break;
		}//当最小的等于最大的时,我们就可以出来了
        
		if(run-a[l]>a[r]-run){//如果a[l]还不满足
			a[r]=run;
			a[l]+=(a[r]-run);
			r--;
			cnt++;
		}
        
		if(run-a[l]<a[r]-run){//如果a[r]还太满足
			a[l]=run;
			a[r]-=(run-a[l]);
			l++;
			cnt++;
		}
        
		if(run-a[l]==a[r]-run){//最好还是
			a[l]=a[r]=run;
			l++;
			r--;
			cnt++;
		}
        
	}
    
}

int main(){
	scanf("%d",&n);
    
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);//读入
		run+=a[i];
	}
    
	run/=n;//我们的目标
    
	sort(a+1,a+n+1);//有可怜的朋友可能看不懂这里,不过没关系,只要知道它是在排序就行了
    
	bracsort();
    
	printf("%d",cnt);
	return 0;
}

至于错误的问题嘛,就不用我说了。

让我们思考思考……

见下—>

C.正确分析

既然我们想让他们全部相等,那么何不一一攻破?

确立方向

首先需要的是确立要前进的方向。

及最后它们要成为什么样子。

这里我们可以设一个go for it变量或者run变量:

int n,a[101],run=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
   scanf("%d",&a[i]);
   run+=a[i];
}
run/=n;

这里使用的是小学二年级法。

设置标记变量

在这里我们要设计一个标记变量,比如cnt。

int cnt=0;//初始化是一个常忘的问题

紧接着我们需要设计cnt怎么计数。

其实很简单。

如果我请别人给我一点,那么cnt就+1。

for(int i=1;i<=n;i++){
    if(/*a[i]请别人借他一点*/)
        cnt++;
}

什么时候需要借?

这是一个关键,什么时候需要去接?

你看看题目中的规则:

在编号为1堆上取的纸牌,只能移到编号为2的堆上;

在编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;

其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

既然只能这样,那么我们发现有a[i]不足就借呗。

if(a[i]<run){
    //借
    cnt++;
}

怎么去借呢?我们这里统一向后借吧:

a[i+1]-=(run-a[i]);
a[i]=run;

然而还有一种情况:手中事情太多!!!

if(a[i]>run){
    a[i+1]+=(a[i]-run);
    a[i]=run;
}

不知大家发现了吗,这样一来,算到最后,我们的a[n]自然不用接了。

于是循环改写成:

for(int i=1;i<n;i++){
    
}

循环内部我们也可以改一改:

if(a[i]!=run){
    a[i+1]=a[i+1]+a[i]-run;;
    a[i]=run;
    cnt++;
}

D.代码全套亮相

#include<cstdio>

using namespace std;

int main(){
	int n,a[101],run=0;
    
	scanf("%d",&n);
    
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		run+=a[i];
	}
    
	run/=n;
    
	int cnt=0;
    
	for(int i=1;i<n;i++){
        
		if(a[i]!=run){
            
			a[i+1]=a[i+1]+a[i]-run;
			a[i]=run;
			cnt++;
            
		}
        
	}
    
	printf("%d",cnt);
	return 0;
}

其实一看,还蛮简单的。

根本不像一道贪心题~~。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值