http://172.20.6.3/Problem_Show.asp?id=1540
之前莫比乌斯反演也写了一道这种找规律分块计算的题,没觉得这么恶心啊。
具体解释看代码。
翻硬币的具体方法就是分别算出每个单个正面朝上的情况的sg函数然后异或。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 #include<iostream> 6 #include<map> 7 #include<ctime> 8 using namespace std; 9 int n,k,w,mx; 10 int f[100010][2]={};//n/x和n/(n/x)代表的x的sg值,分成两部分使得储存需要的空间开根 11 //值得注意的是,这两种划分方式分出的x的范围是一定的,大概是数字的性质。 12 int vis[100010]={}; 13 int doit(int x,int y){return x==y?x+1:y/(y/(x+1));} 14 int main(){ 15 scanf("%d%d",&n,&k); 16 //暴力代码 17 /*for(int i=n;i>=1;i--){ 18 int now=0; 19 for(int j=i+i;j<=n;j+=i){ 20 now^=f[j];vis[now]=i; 21 } 22 for(int j=1;;j++){ 23 if(vis[j]!=i){ 24 f[i]=j; 25 break; 26 } 27 } 28 }*/ 29 /*暴力打表后可以发现这个东西满足规律: 30 只要n/x(向下取整)相等sg值就相等。 31 于是就开始了漫长艰辛的分块计算之旅 32 倒数真有趣 33 _ 34 | | 35 | | 36 _ _| |_ _ 37 | | | | | | 38 \_________/ 39 */ 40 mx=(int)sqrt(double(n)); 41 for(int i=1;i<=n;i=doit(i,n)){//这里枚举的i从小到大,其实是n/x 42 int y,now=0,z; 43 for(int j=2;j<=i;j=doit(j,i)){//上一层枚举了n/x,这一层用同样的方式枚举他的倍数 44 y=i/j;//既然用n/x表达,扩大j倍自然是/j。 45 z=y<=mx?f[y][0]:f[n/y][1]; 46 vis[now^z]=i; 47 if((i/y-i/(y+1))&1)now^=z; 48 //如果在范围里有偶数个z,那算下一个的范围的时候z就被自己消掉了所以不用算。 49 } 50 for(int j=1;;j++){//大家喜闻乐见的mex时间 51 if(vis[j]!=i){ 52 if(i<=mx)f[i][0]=j;else f[n/i][1]=j; 53 break; 54 } 55 } 56 } 57 for(int i=1;i<=k;i++){ 58 scanf("%d",&w);int x,ans=0; 59 for(int i=1;i<=w;i++){ 60 scanf("%d",&x); 61 x=n/x; 62 ans^=x<=mx?f[x][0]:f[n/x][1];//喜闻乐见的异或时间 63 } 64 if(ans)printf("Yes\n");//喜闻乐见的判定时间 65 else printf("No\n"); 66 } 67 return 0; 68 }