Gym102904 A Coins 题解 思维
https://codeforces.com/gym/102904
题意:N枚硬币(有鹰面(eagle)和尾面(tail))排成一排,有k个鹰面朝上就翻转第k枚硬币,直到所有硬币都尾面朝上,问要翻转多少次
样例解释:
样例1的12步翻转:00101->01101->01001->00001->10001->11001->11101->11111->11110->11100->11000->10000->00000
直接模拟,反正数组可以随机访问 (开玩笑的,会TL test8的)
一个小发现(提示):如果N枚硬币全部都是1,那么需要翻多少次,你发现他翻硬币有什么规律?
题解:我们发现翻硬币有一个单调性,如果当前是把0变成1,那么k变大,我们会继续去翻右边的一枚硬币,如果是吧1翻成0,那k变小我们翻左边一枚
这导致一个有趣的情况:假如当前要翻第k枚,然后第k枚和右边a+1枚都是0,那么我们会从第k枚开始,一枚一枚地从左往右将第k~k+a枚都翻成1; 第k枚是1并且左边有若干枚1同理,会从右往左一枚一枚全翻成0
这样单调地翻转后,走过的一条路全部都变成了1(或0)
我们又发现,在上面的第一种情况中一旦遇到一枚原为1的硬币,我们翻了他,那我们就进行第二种情况的模拟了(看起来像是掉头),
所以最终我们翻硬币的整个流程是左右左右掉头然后全部翻成0
经过上面的论述,发现其实不存在输出-1的情况,我们只需要记录每次掉头的下标再 做差就能不一步一步模拟而只是模拟掉头地去计算答案.
进一步发现,掉头点即为起点(一开始的k)开始往左的0和往右的1.(因为运动的方向由当前翻转的硬币决定(当前是0则往右,是1则左))
样例1的掉头模拟:(对照着上面一步一步的来看)
从2开始到3掉一次头向左,翻1枚,变成01101
从3开始到1掉一次头向右,翻2枚,变成00001
从1开始到5掉一次头向左,翻4枚,变成11111
从5开始一直翻到开头,翻5枚变成00000
代码写起来有几个小细节,建议自己想清楚每次掉头要走多少步再写代码:
#include<iostream> #include<algorithm> #include<string> #include<string.h> #include<cmath> #define rep(i,a,b) for(ll i=a;i<=b;++i) #define per(i,a,b) for(ll i=a;i>=b;--i) #define fi first #define se second #define mp make_pair #define all(x) x.begin(),x.end() using namespace std; typedef long long ll; const int maxn=1e5+10,inf=0x3f3f3f3f; bool coins[maxn]={0}; int head[maxn],tail[maxn],nhead=0,ntail=0; int main() { int n;cin>>n; string s;cin>>s; int num=0; rep(i,1,n) { coins[i]=s[i-1]-'0'; if(coins[i]) { num++; } } per(i,num,1) if(!coins[i]) head[++nhead]=i; head[++nhead]=0; rep(i,num,n) if(coins[i]) tail[++ntail]=i; ll ans=0; //首先翻1 if(coins[num]){ //可证:ntail其实等于nhead ans=tail[1]-head[1]; rep(i,2,nhead){ ans+=tail[i]-head[i-1]; //向右翻1 ans+=tail[i]-head[i]; //向左翻0 } } //首先翻0 else{ //可证:ntail其实等于nhead-1 rep(i,1,ntail){ ans+=tail[i]-head[i]; //向右翻1 ans+=tail[i]-head[i+1]; //向左翻0 } } cout<<ans<<endl; return 0; } /* 9 100110110 25 */
一个小发现,最多需要的步数是(N+1)*N/2,这样的硬币排列是前一半都是0,后一半都是1,例如N=4时0011,N等于5时00111
N最大是1e5,所以用int就够了(WA15警告),用longlong哈