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");
}
}
}

Codeforces - 1191E - Tokitsukaze and Duel - 博弈论 - 尺取的更多相关文章

  1. B. Complete the Word(Codeforces Round #372 (Div. 2)) 尺取大法

    B. Complete the Word time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  2. Codeforces 1190C. Tokitsukaze and Duel

    传送门 注意到后手可以模仿先手的操作,那么如果一回合之内没法决定胜负则一定 $\text{once again!}$ 考虑如何判断一回合内能否决定胜负 首先如果最左边和最右的 $0$ 或 $1$ 距离 ...

  3. Codeforces 1190C Tokitsukaze and Duel game

    题意:有一个长为n的01串,两个人轮流操作,每个人可以把某个长度为m的区间变成相同颜色,谁在操作后整个串颜色相同就赢了.问最后是谁赢?(有可能平局) 思路:容易发现,如果第一个人不能一击必胜,那么他就 ...

  4. [Codeforces 1191D] Tokitsukaze, CSL and Stone Game(博弈论)

    [Codeforces 1191D] Tokitsukaze, CSL and Stone Game(博弈论) 题面 有n堆石子,两个人轮流取石子,一次只能从某堆里取一颗.如果某个人取的时候已经没有石 ...

  5. Codeforces Round #116 (Div. 2, ACM-ICPC Rules) E. Cubes (尺取)

    题目链接:http://codeforces.com/problemset/problem/180/E 给你n个数,每个数代表一种颜色,给你1到m的m种颜色.最多可以删k个数,问你最长连续相同颜色的序 ...

  6. Educational Codeforces Round 53 (Rated for Div. 2) C. Vasya and Robot 【二分 + 尺取】

    任意门:http://codeforces.com/contest/1073/problem/C C. Vasya and Robot time limit per test 1 second mem ...

  7. 【尺取或dp】codeforces C. An impassioned circulation of affection

    http://codeforces.com/contest/814/problem/C [题意] 给定一个长度为n的字符串s,一共有q个查询,每个查询给出一个数字m和一个字符ch,你的操作是可以改变字 ...

  8. Codeforces 660 C. Hard Process (尺取)

    题目链接:http://codeforces.com/problemset/problem/660/C 尺取法 #include <bits/stdc++.h> using namespa ...

  9. Codeforces 939E Maximize! (三分 || 尺取)

    <题目链接> 题目大意:给定一段序列,每次进行两次操作,输入1 x代表插入x元素(x元素一定大于等于之前的所有元素),或者输入2,表示输出这个序列的任意子集$s$,使得$max(s)-me ...

随机推荐

  1. KNN算法项目实战——改进约会网站的配对效果

    KNN项目实战——改进约会网站的配对效果 1.项目背景: 海伦女士一直使用在线约会网站寻找适合自己的约会对象.尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人.经过一番总结,她发现自己交往过的人可 ...

  2. Linux--shell三剑客<sed>--07

    1.sed(stream editor): 作为行编辑器,对文本进行编辑(以行为单位) 默认显示输出所有文件内容 注意:sed编辑文件,却不改变原文件 2.sed的工作原理: 指定一个文本文件,依次读 ...

  3. liunx-centos-基础命令详解(1) -主要内容来自 —https://www.cnblogs.com/caozy/p/9261224.html

    关机:halt/poweroff :立刻关机reboot :立刻重启 shutdown -r now :立刻重启shutdown -h 00:00 :定时重启 now:立刻shutdown -h +n ...

  4. ps:界面概览

    首先我们来认识一下Photoshop的界面组成,如下图是一个典型的界面.为了方便识别,我们加上了颜色和数字. 1:顶部的红色区域是菜单栏,包括色彩调整之类的命令都存放在从菜单栏中.在我们的教程中使用[ ...

  5. 【LGR-062】洛谷10月月赛 III div.2 (A-C)

    前言 100+100+46+0=246pts 300多名 以后每次比赛都要有进步哦!qwq 小D与笔试 水题 Code #include<algorithm> #include<io ...

  6. php 弱类型比较

    1.按数字值比较 1.1数字(整数.浮点数.科学计数法.各种进制数)或纯十进制数字字符串. <?php $a = 100; //整数 $b = "100"; //十进制数字符 ...

  7. nginx配置虚拟主机-端口号区分/域名区分

    Nginx实现虚拟机 可以实现在同一台服务运行多个网站,而且网站之间互相不干扰.同一个服务器可能有一个ip,网站需要使用80端口.网站的域名不同. 区分不同的网站有三种方式:ip区分.端口区分.域名区 ...

  8. Xcode出现报错,但是没有给出详细信息,可以看这里

    Xcode出现报错,"Xcode build:clang: error: linker command failed with exit code 1 (use -v to..." ...

  9. Xenu Link Sleuth 简单好用的链接测试工具

    XenuLink Sleuth 名词介绍 “Xenu链接检测侦探”是被广泛使用的死链接检测工具.可以检测到网页中的普通链接.图片.框架.插件.背景.样式表.脚本和java程序中的链接. 那么神马时候出 ...

  10. 【HDOJ6685】Rikka with Coin(DP)

    题意:有10,20,30,100四种硬币,每种数量无限,给定n个a[i],问能组成所有a[i]的硬币的最小总数量 n<=1e2,a[i]<=1e9 思路: #include<bits ...