zoukankan      html  css  js  c++  java
  • Codeforces

    https://codeforc.es/contest/1191/problem/E

    参考自:http://www.mamicode.com/info-detail-2726030.html
    和官方题解。

    首先这种组合游戏,必定存在一种不败策略。一种很直观的理解就是,假如没有办法一开始就胜利,那么就优先考虑和局的。
    假如有连续k个一样的,那么先手可以进行一次无效翻转变成后手,所以这种情况先手必然至少和局。这样的话一次翻转之后就必然有连续k个一样的,后手也必定有了至少和局的机会,他也进行一次无效翻转。所以先手要赢的话,只能够在第一步直接赢,否则后手拥有了不败策略之后先手就不可能会有机会赢。

    这种情况就判断除了某个k连续区间之外其他的是否全0或者全1,可以用前缀和O(n)预处理然后O(n)判断。

    否则,一开始没有连续k个一样的,先手不可以进行一次无效的翻转。这种时候后手能赢的必要条件是在先手翻转了之后,后手第二步可以直接赢,否则后手虽然拥有了不败策略,但第三步开始先手也有了不败策略,会导致和局。那什么情况下可以获胜呢?是先手无论选择翻转哪一段,都会把情况转变为上面的情况。所以有一种优化是,后手能赢的必要条件是2*k>=n。因为先手一开始可以选择翻0或者翻1,后手必定要在下一次把全部翻成0或者全部翻成1才能获胜,否则先手一开始会捣乱,把一段连续k个的区间翻转到比如数字c,后手最佳策略必定是贪心在这个区间的两侧第一个非c开始再翻k个相同数字,即使这样也会有至少1个不一样,那么后手就失去了第二步赢的机会了。

    所以就要判断无论翻转哪k个到c,剩下的非c必定集中存在于k区间的左侧或者右侧,且他们连续于k个位置,这样才能导致后手必胜。怎么判断呢?枚举先手翻转位置的起点,然后判断“集中于同侧?”,然后判断“连续于k个位置?”。“集中于同侧”这个好办,就选最左侧和最右侧的0和1记录下来O(1)验证是不是被包含在k区间里面。“连续于k个位置”就要找到k区间的向左或向右的最近的第一个非c位置x,然后看最左或者最右的非c(假如存在)是不是在[x-k+1,x]位置或者[x,x+k-1]内,这样是用尺取O(n)判断。

    写起来好多bug,很多地方复制粘贴之后没改过了,可能是老了吧。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n, k;
    char s[100005];
    int sum[2][100005];
    
    inline int range_sum(int i, int l, int r) {
        return sum[i][r] - sum[i][l - 1];
    }
    
    bool judge_f() {
        for(int cp = 1; cp + k - 1 <= n; cp++) {
            if(range_sum(0, 1, cp - 1) + range_sum(0, cp + k, n) + k == n) {
                return true;
            } else if(range_sum(1, 1, cp - 1) + range_sum(1, cp + k, n) + k == n) {
                return true;
            }
        }
        return false;
    }
    
    int lm0, lm1, rm0, rm1;
    int ln0, ln1, rn0, rn1;
    
    void init() {
        //先手不能赢,那肯定至少有一个0和一个1
        for(int i = 1;; i++) {
            if(s[i] == '0') {
                lm0 = i;
                break;
            }
        }
        for(int i = 1;; i++) {
            if(s[i] == '1') {
                lm1 = i;
                break;
            }
        }
        for(int i = n;; i--) {
            if(s[i] == '0') {
                rm0 = i;
                break;
            }
        }
        for(int i = n;; i--) {
            if(s[i] == '1') {
                rm1 = i;
                break;
            }
        }
    }
    
    bool judge_s() {
        init();
        //判断先手翻转成0
        rn1 = k + 1;
        ln1 = -1;//一开始左边就是没有最近的1
        while(s[rn1] != '1')
            rn1++;//肯定有至少一个不能覆盖的1,否则judge_f
    
        for(int cp = 1; cp + k - 1 <= n; cp++) {
            if(lm1 < cp && rm1 > cp + k - 1) {
                return false;//两侧都有1
            } else {
                if(lm1 < cp) {
                    //左侧有1,而右侧没有
                    //找左侧最近的1的下标,用尺取可以,每次cp移动的时候更新最近的1
                    //左边不可能是-1啊
                    if(lm1 < ln1 - k + 1)
                        return false;
                } else {
                    //右侧有1,因为两边都没有的话在judge_f判断过了
                    //找右侧最近的1的下标,用尺取也可以,每次cp移动的时候更新最近的1
                    //右边也不可能是-1啊
                    if(rm1 > rn1 + k - 1)
                        return false;
                }
            }
            if(s[cp] == '1') {
                ln1 = cp;
            }
            if(rn1 == cp + k) {
                //rnl会被新区间覆盖掉
                rn1++;
                if(rn1 > n)
                    rn1 = -1;
            }
            while(rn1 != -1 && s[rn1] != '1') {
                rn1++;
                if(rn1 > n) {
                    rn1 = -1;
                    break;
                    //找不到右侧最近的1
                }
            }
            //查找下一个最近的,没有的话就是-1
        }
        //
        //
        //判断先手翻转成1
        rn0 = k + 1;
        ln0 = -1;
        while(s[rn0] != '0') {
            rn0++;
            //肯定有至少一个不能覆盖的0,否则judge_f
        }
        //一开始左边就是没有最近的0
        for(int cp = 1; cp + k - 1 <= n; cp++) {
            if(lm0 < cp && rm0 > cp + k - 1) {
                //两侧都有0
                return false;
            } else {
                if(lm0 < cp) {
                    //左侧有0,而右侧没有
                    //找左侧最近的0的下标,用尺取可以,每次cp移动的时候更新最近的0
                    //左边不可能是-1啊
                    if(lm0 < ln0 - k + 1)
                        return false;
                } else {
                    //右侧有0,因为两边都没有的话在judge_f判断过了
                    //找右侧最近的0的下标,用尺取也可以,每次cp移动的时候更新最近的0
                    //右边也不可能是-1啊
                    if(rm0 > rn0 + k - 1)
                        return false;
                }
            }
            if(s[cp] == '0') {
                ln0 = cp;
            }
            if(rn0 == cp + k) {
                //rn0会被新区间覆盖掉
                rn0++;
                if(rn0 > n)
                    rn0 = -1;
            }
            while(rn0 != -1 && s[rn0] != '0') {
                rn0++;
                if(rn0 > n) {
                    rn0 = -1;
                    break;
                    //找不到右侧最近的0
                }
            }
            //查找下一个最近的,没有的话就是-1
        }
        return true;
    }
    
    int main() {
    #ifdef Yinku
        freopen("Yinku.in", "r", stdin);
        //freopen("Yinku.out", "w", stdout);
    #endif // Yinku
        while(~scanf("%d%d%s", &n, &k, s + 1)) {
            sum[0][0] = 0, sum[1][0] = 0;
            for(int i = 1; i <= n; i++) {
                sum[0][i] = sum[0][i - 1] + (s[i] == '0');
                sum[1][i] = sum[1][i - 1] + (s[i] == '1');
            }
            if(judge_f()) {
                puts("tokitsukaze");
            } else if(judge_s()) {
                puts("quailty");
            } else {
                puts("once again");
            }
        }
    }
    

    有一些多余的操作,去掉之后就是:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n, k;
    char s[100005];
    int sum[100005];
    
    inline int range_sum(int l, int r) {
        return sum[r] - sum[l - 1];
    }
    
    bool judge_f() {
        for(int cp = 1; cp + k - 1 <= n; cp++) {
            int tmp = range_sum(1, cp - 1) + range_sum(cp + k, n);
            if(tmp == 0 || tmp + k == n)
                return true;
        }
        return false;
    }
    
    int lm0, lm1, rm0, rm1;
    int ln0, ln1, rn0, rn1;
    
    void init() {
        //先手不能赢,那肯定至少有一个0和一个1
        lm0 = -1, lm1 = -1, rm0 = 1, rm1 = 1;
        for(int i = 1;i<=n; i++) {
            if(s[i] == '0') {
                lm0 = (lm0 == -1) ? i : lm0;
                rm0 = i;
            } else {
                lm1 = (lm1 == -1) ? i : lm1;
                rm1 = i;
            }
        }
    }
    
    bool judge_s() {
        init();
        //判断先手翻转成0
        rn1 = k + 1;
        while(s[rn1] != '1')
            rn1++;//肯定有至少一个不能覆盖的1,否则judge_f
        for(int cp = 1; cp + k - 1 <= n; cp++) {
            if(lm1 < cp && rm1 > cp + k - 1)
                return false;//两侧都有1
            else {
                if(lm1 < cp && lm1 < ln1 - k + 1)
                    return false;
                else if(rm1 > rn1 + k - 1)
                    return false;
            }
            if(s[cp] == '1')
                ln1 = cp;
            if(rn1 == cp + k)
                rn1++;
            while(rn1 <= rm1 && s[rn1] != '1')
                rn1++;
        }
        //判断先手翻转成1
        rn0 = k + 1;
        while(s[rn0] != '0')
            rn0++;
        for(int cp = 1; cp + k - 1 <= n; cp++) {
            if(lm0 < cp && rm0 > cp + k - 1)
                return false;//两侧都有0
            else {
                if(lm0 < cp && lm0 < ln0 - k + 1)
                    return false;
                else if(rm0 > rn0 + k - 1)
                    return false;
            }
            if(s[cp] == '0')
                ln0 = cp;
            if(rn0 == cp + k)
                rn0++;
            while(rn0 <= rm0 && s[rn0] != '0')
                rn0++;
        }
        return true;
    }
    
    int main() {
    #ifdef Yinku
        freopen("Yinku.in", "r", stdin);
        //freopen("Yinku.out", "w", stdout);
    #endif // Yinku
        while(~scanf("%d%d%s", &n, &k, s + 1)) {
            sum[0] = 0;
            for(int i = 1; i <= n; i++) {
                sum[i] = sum[i - 1] + s[i]-'0';
            }
            if(judge_f()) {
                puts("tokitsukaze");
            } else if(judge_s()) {
                puts("quailty");
            } else {
                puts("once again");
            }
        }
    }
    
  • 相关阅读:
    剑指offer python版 正则表达式匹配
    剑指offer python版 删除链表中重复的结点
    剑指offer python版 在O(1)时间删除链表结点
    剑指offer python版 打印1到最大的n位数
    剑指offer python版 数值的整数次方
    剑指offer python版 二进制中1的个数
    剑指offer python版 剪绳子
    剑指offer python版 机器人的运动范围
    设计模式(二) 工厂模式
    设计模式(一) 单例模式
  • 原文地址:https://www.cnblogs.com/Yinku/p/11183784.html
Copyright © 2011-2022 走看看