均分纸牌问题的分析:
均分纸牌问题:有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 N=4,4 堆纸牌数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取 4 张牌放到 ④ (9 8 13 10) -> 从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。
先放代码在分析吧,代码比较短。(转自https://www.cnblogs.com/yuchenlin/p/4382027.html)
int main(){
int N;
int pokers[MAX];
cin>>N;
int total = 0;
for (int i=0; i<N; i++) {
cin>>pokers[i];
total+=pokers[i];
}
int avg = total/N,times=0;
for(int i=0;i<N;i++){
if(pokers[i]!=avg){
pokers[i+1] -= avg - pokers[i]; //重要!
times++;
}
}
cout<<times<<endl;
}
可以看到最核心的那个循环的思想是这样的:
从第一堆牌开始处理,如果第一堆牌整好是avg那么就放在一边不管了。
如果第一堆牌不是avg,那么就要把第二堆牌(合法的移动只有从2移到1,这也是这个算法的精髓之处)移动几张到第一堆,恰好使第一堆等于avg,从而只考虑第二堆开始到第N堆为止这些堆如何搞的子问题。然后依次递归下去。
这里的一个小技巧是认为牌数可以为负数,这样才能继续下去。综上,这个步骤是合理的。但是看不出来是最优的。可见,贪心法确实是比较容易实现,因为比较符合人类直觉,但是不好证明。
再反过来看一下前面提到的几点,可行性满足,不可取消,每一次操作都是直接赋值,局部最优,当前情况下,只能从右往左移动,且贪心地想尽快让第一堆满足约束。
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
long long a[100005];
long long b[100005];
int main()
{
int T;
cin>>T;
while(T--)
{
long long n ;
cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&b[i]);
long long sum=0;
for(int i=1;i<=n-1;i++)
{
if(a[i]!=b[i])
{
a[i+1]-=(b[i]-a[i]); //注意b[i]和a[i]相减顺序
sum+=abs(b[i]-a[i]);
}
}
printf("%lld\n",sum);
}
return 0;
}
关键语句
a[i+1]-=(b[i]-a[i]); //注意b[i]和a[i]相减顺序
意思是,从第一堆开始到倒数第二堆:如果本堆少了,就从右边的那一堆里拿泥巴过来,否则就拿泥巴给右边那堆。(同样是注意认为泥堆可以为负数)。
就这样下去,到最后一堆一定是==b[i]即满足条件的。(不然这题无解)
这道题是埃森哲杯第十六届上海大学生程序设计大赛的A题,自己还是现去学习了纸牌均分问题才明白的.....还是菜了.....加油 !