zoukankan      html  css  js  c++  java
  • CodeForces 1091H

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

    题意见洛谷里的翻译。

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

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

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

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

    那么SG函数怎么求呢?如果等到求第(i)个局面的时候再统计后继局面的SG函数值们,那么无疑ICG的DAG中的每一条有向边都要被访问一次,而那是(left(2 imes10^5 ight)^2=4 imes10^{10})级的,肯定会TLE。于是我们可以换一种思路,在求出第(i)个局面时,就把所有它的前驱局面标记一下:“你们都有一个后继局面(就是我)的SG函数值为(mathrm{SG}( ext{第}i ext{个局面}))了!”聪明的读者可能要问了,这不也要访问每条边吗?是的。不过这种方法可以用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优化才能勉强卡进(4mathrm{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
    Bob":"Bob
    Alice");//非0先手必胜,否则先手必败
    	return 0;
    }
    
  • 相关阅读:
    Runoob-Java:Java 开发环境配置
    公司-科技:JotSpot
    服务-WiKi:WiKi
    国际组织-Java:JCP
    Java:Java 简介
    Runoob-Java:Java 教程
    oracle-ofa
    HTML5 viewport 标签与 CSS3 background-size 属性 使图片完全适应区域内容
    Android studio怎么导入Recycler VIew
    Android studio怎么导入Recycler VIew
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-1091H.html
Copyright © 2011-2022 走看看