题目描述
零崎总是说自己有一百种梗可玩,然而其实都是假的,是化学成分的,是特技。想要给摊还分析加个梗,实在是不好想,因为这个内容并不是什么算法,而是算法分析。
不过既然还得考,那么没有办法……
“势能法”是摊还分析中一种比较简单常用的方法,而且容易理解。现在零崎有K个硬币,规定每次“翻动”操作只能从最右侧开始翻转硬币,且如果把一个硬币从正面向上翻到背面向上,则需要对其左侧相邻的那个硬币也执行“翻动”操作(翻至最左则结束)。定义硬币组的势为硬币中正面向上的硬币的个数。现在要求你求出从某个给定的硬币组状态之后连续N个“翻动”操作的摊还代价。
硬币组的初始状态以一组数表示,0代表反面,1代表正面。
输入
多组测试数据。
每组测试数据共两行:
第一行K+1个正整数,分别为硬币组中硬币数K和初始状态(0<K<20);
第二行一个正整数,为题目要求你求出的“翻动”操作的个数N(0<N<30)。
输出
每组测试数据输出一行,此行第i个数输出初始状态后第i个操作的摊还代价(1<=i<=N)。
输入样例
3 0 0 0
2
输出样例
2 2
题目来源:http://biancheng.love/contest/23/problem/F/index
所谓摊还分析:求数据结构中的一个操作序列中所执行的所有操作的平均时间,来评价操作的代价。我们可以说明一个操作的平均代价很低,即使序列中某个单一操作的代价很高。瘫痪分析不同于平均情况分析,它不涉及到概率问题,它可以保证最坏的情况下每个操作的平均性能。
解题思路:在计算摊还代价时可以采用:聚合分析、核算法、势能算法。参考算法导论书中的例子:二进制计数器递增问题。通过题目要求可以得到,反转硬币其实也就是实现二进制计数器的递增问题,在摊还代价计算中得到如果反转到全为0,摊还代价为0,其他情况均为1,因此可以采用简单粗暴的方法。计算对应二进制的十进制数,由于反转硬币的摊还代价是有周期的(在给定二进制位数之后,周期为2^k);因此只需要判断是输出0还是输出2就可以了。
本题代码:
1 #include <bits/stdc++.h> 2 #define max_size 21 3 int a[max_size]; 4 using namespace std; 5 int main() 6 { 7 int k,num,flag,N; 8 while(~scanf("%d",&k)) 9 { 10 num=0; 11 flag=pow(2,k); 12 for(int i=1;i<=k;i++) 13 scanf("%d",&a[i]); 14 for(int i=1;i<=k;i++) 15 num+=a[i]*pow(2,k-i); 16 scanf("%d",&N); 17 int temp=flag-num; 18 for(int i=1;i<=N;i++) 19 { 20 if(i<=temp-1) 21 { 22 if((i-1)==temp) 23 printf("0 "); 24 else 25 printf("2 "); 26 } 27 else if(i>=temp) 28 { 29 if((i+num)%flag==0) 30 printf("0 "); 31 else 32 printf("2 "); 33 } 34 } 35 printf(" "); 36 } 37 }
采用判断是否所有位数都为0,来进行输出的另外一种方法:
1 #include<iostream> 2 using namespace std; 3 int main() 4 { 5 int N; 6 while(cin>>N) 7 { 8 int A[N],k; 9 for(int i=0; i<N; i++) 10 { 11 cin>>A[i]; 12 } 13 cin>>k; 14 int j=N-1; 15 for(int i=1; i<=k; i++) 16 { 17 while(j>=0 && A[j]==1) 18 { 19 A[j]=0; 20 j--; 21 } 22 if(j>=0) 23 { 24 A[j]=1; 25 cout<<"2 "; 26 } 27 else 28 { 29 for(int k=0; k<N; k++) 30 A[k]=0; 31 cout<<"0 "; 32 } 33 j=N-1; 34 } 35 cout<<endl; 36 } 37 }