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

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

A.遇到一个问题

阿瓜是一个强迫症人……

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

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

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

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

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

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

请你帮帮可怜的阿瓜吧!

输入

\(N\)\(N\)堆纸牌,\(1\leq N\leq 100\)) \(A_1 A_2 … A_n\)(N堆纸牌,每堆纸牌初始数,\(1\leq Ai\leq 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;
}

其实一看,还蛮简单的。

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

posted @ 2020-04-18 17:05  AlienCollapsar  阅读(79)  评论(0编辑  收藏  举报
// 生成目录索引列表 // ref: http://www.cnblogs.com/wangqiguo/p/4355032.html // modified by: zzq