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
*/
View Code

 

一个小发现,最多需要的步数是(N+1)*N/2,这样的硬币排列是前一半都是0,后一半都是1,例如N=4时0011,N等于5时00111

N最大是1e5,所以用int就够了(WA15警告),用longlong哈

posted @ 2021-02-28 14:10  Laozhu1234  阅读(89)  评论(0编辑  收藏  举报