CodeForces 1091H New Year and the Tricolore Recreation

洛谷题目页面传送门 & CodeForces题目页面传送门

题意见洛谷里的翻译。

刚看题目,心想:完了,这题可执行操作与轮到的玩家有关,不是ICG,不能用SG函数了。

这么想就自闭了,题目就是想迷惑你。游戏结束的条件是无法移动,而整个棋盘是无线宽的,游戏永远无法因棋子碰到边界而结束,只能因为两棋子紧挨无法移动而结束。也就是说,棋子的具体坐标并不重要,相对距离才重要。那么将左(右)边\(2\)个棋子右(左)移和将右(左)边\(1\)个棋子左(右)移有什么区别呢?

经过一番转化,Alice和Bob的操作都变成了将左边\(1\)个棋子右移或将右边\(1\)个棋子左移。不难发现,中间棋子永远不动。那么一行的游戏可以拆分成两个子ICG:每次操作可将左边的棋子与中间的棋子的距离缩小\(\bm{d(d}\textbf{是质数或是2个质数的乘积}\bm{,d\ne f)}\),距离为\(1\)时不能再缩小……可将右边的棋子……。拆分开后,左和右本质上就没差别了。

因为\(2\)个棋子之间的距离最多是\(10^5-\left(-10^5\right)=2\times10^5\),所以我们只需要求出\(\forall d\in[1,2\times10^5],\mathrm{SG}(\text{左(右)棋子与中间棋子相距}d\text{时的局面})\),然后再将\(1\)\(2\)个,共\(2n\)个子ICG的SG函数值异或起来,如果为\(0\)先手必败,否则先手必胜。

那么SG函数怎么求呢?如果等到求第\(i\)个局面的时候再统计后继局面的SG函数值们,那么无疑ICG的DAG中的每一条有向边都要被访问一次,而那是\(\left(2\times10^5\right)^2=4\times10^{10}\)级的,肯定会TLE。于是我们可以换一种思路,在求出第\(i\)个局面时,就把所有它的前驱局面标记一下:“你们都有一个后继局面(就是我)的SG函数值为\(\mathrm{SG}(\text{第}i\text{个局面})\)了!”聪明的读者可能要问了,这不也要访问每条边吗?是的。不过这种方法可以用bitset卡掉\(32\)的常数。

设所有可移动的格子数存在一个bitset\(ok\)里(可移动为\(1\),否则为\(0\));bitset数组\(hav\)表示若\(hav_{i,j}\)\(1\),则与中间棋子相距\(j\)的局面有一个后继的SG函数值为\(i\),否则没有。通过打表可发现,任何情况下SG函数值都不会超过\(100\),那么\(hav\)的大小开\(101\)就好。那么与中间棋子相距\(i\)的标记操作为hav[sg[i]]|=ok<<i;。利用这个\(hav\)也可以轻松求SG函数值。至于\(ok\)怎么求……先跑一遍Eratosthenes筛法,再把是质数或是\(2\)个质数的乘积的数set\(ok\)里,再把\(f\)reset掉。

对了,这道毒瘤题要开O3优化才能勉强卡进\(4\mathrm{s}\)的时限。

下面是AC代码:

#pragma GCC optimize(3)//毒瘤O3优化
#include<bits/stdc++.h>
using namespace std;
#define SG 100//SG函数值的上限
#define DIF 200000//2个棋子的距离的上限
#define pb push_back
int taboo/*题目中的f*/,sg[DIF+1]/*sg[i]表示与中间棋子相距i的局面的SG函数值*/;
vector<int> pr;//质数列表
bitset<DIF+1> npr/*是否为合数*/,ok/*能移动的格子数*/,hav[SG+1]/*hav[i][j]表示与中间棋子相距j的局面有没有一个后继的SG函数值为i*/;
void eratosthenes(){//Eratosthenes筛法
    for(int i=2;i<=DIF;i++){
        if(!npr[i])pr.pb(i);
        else continue;
        for(int j=i<<1;j<=DIF;j+=i)npr.set(j);
    }
}
void ok_prework(){//求出ok
    int i;
    for(i=0;i<pr.size();i++)ok.set(pr[i]);//所有质数
    for(i=0;i<pr.size();i++)for(int j=i;j<pr.size();j++){//所有两个质数的乘积
        if(1ll*pr[i]*pr[j]>1ll*DIF)break;
        ok.set(pr[i]*pr[j]);
    }
    ok.reset(taboo);//不能移动taboo格
}
void sg_prework(){//求出所有SG函数值
    for(int i=1;i<=DIF;i++){
        while(hav[sg[i]][i])sg[i]++;//mex运算
        hav[sg[i]]|=ok<<i;//对前驱局面的标记操作
    }
}
int main(){
    eratosthenes();
    int n,totsg=0/*将2n个子ICG的SG函数值异或起来的结果*/;scanf("%d%d",&n,&taboo);
    ok_prework();
    sg_prework();
    while(n--){
        int a,b,c;scanf("%d%d%d",&a,&b,&c);
        totsg^=sg[b-a/*左边棋子与中间棋子的距离*/]^sg[c-b/*右边棋子与中间棋子的距离*/];//异或
    }
    printf(totsg?"Alice\nBob":"Bob\nAlice");//非0先手必胜,否则先手必败
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ycx-akioi/p/CodeForces-1091H.html