jzoj6437. 【GDOI2020模拟01.16】划愤

Description

在这里插入图片描述
在这里插入图片描述

Input

输入共N +1行:
在这里插入图片描述

Output

在这里插入图片描述

Sample Input

Sample Input1:
2
1 2
3 4

Sample Input2:
2
1 2
2 3

Sample Output

Sample Output1:
xiaoDyingle

Sample Output2:
xiaoDwandanle

Data Constraint

在这里插入图片描述

赛时

题意:现在有 N ! N! N!个N维向量,然后每次找到一个向量x使得向量x中每个元素都大于0。再自己搞出一个新的向量y,使得向量y中每个元素都小于向量x中每个元素。接着选取其中任意多个元素替换掉x,那么就产生了 2 n 2^n 2n个新向量。再删掉向量x。
最后谁没得操作谁就GG了。答案你懂得。

比赛当然木有任何的思路,甚至样例都没有推出来。
直接输出先手胜利有25分,特判一个奇妙的东东有70分,加上暴力有90分。
无奈.jpg

题解

这题是真的奇妙♂。网上只有一个论文,关于二维的,看了好久才看懂。
然后这道题还™的是多维的。
苦想了我两晚上。

首先我们来康康二维的:
二维的问题其实相当于这样:

给你一个平面,然后其中有很多硬币,有的是正的,有的是反的。
现在你要选择一个矩形使得其中右下角的硬币是正的,然后把四个顶点都翻转。
求先手是否必胜。

显然我们利用奇妙的方法做,只会暴力。

NIM积

那么我们就引出这个NIM积的概念:
x ⊗ y = m e x 0 < = a < x , 0 < = b < y ( ( a ⊗ b ) ⊕ ( a ⊗ y ) ⊕ ( x ⊗ b ) ) x\otimes y=mex_{0<=a<x,0<=b<y}((a\otimes b)\oplus(a\otimes y)\oplus(x\otimes b)) xy=mex0<=a<x,0<=b<y((ab)(ay)(xb))
其中 ⊕ \oplus 表示异或。(NIM和)
打表可得:
在这里插入图片描述
发现什么性质?
x ⊗ 0 = 0 ⊗ x = 0 x\otimes 0=0\otimes x=0 x0=0x=0
x ⊗ 1 = 1 ⊗ x = x x\otimes 1=1\otimes x=x x1=1x=x
x ⊗ y = y ⊗ x x\otimes y=y\otimes x xy=yx
其实用处不大。
有用的是数学家们弄出来的结论:
如果把 ⊕ \oplus 看做+,把 ⊗ \otimes 看做*
那么这玩意儿满足任何的加和乘的结合律、分配率、交换律等等。

证明我不肥。

然后还有更为惊骇世俗的结论,有关费马数。
x , y < 2 2 k , x ⊗ y < 2 2 k x,y<2^{2^k},x\otimes y<2^{2^k} x,y<22k,xy<22k
x ∗ 2 2 k = x ⊗ 2 2 k x*2^{2^k}=x\otimes 2^{2^k} x22k=x22k
2 2 k ⊗ 2 2 k = 3 2 2 2 k 2^{2^k}\otimes2^{2^k}=\frac 3 22^{2^k} 22k22k=2322k

证明我当然不肥。

只要会用就好了:
我们考虑求x和y的NIM积:(设 x < y x<y x<y
找出最大的k满足: 2 2 k < = x 2^{2^k}<=x 22k<=x M = 2 2 k M=2^{2^k} M=22k
那么x和y可以表达成:
x = a ∗ M + b , y = p ∗ M + q x=a*M+b,y=p*M+q x=aM+b,y=pM+q
那么 x ⊗ y x\otimes y xy(为了方便,下面都用*或+表示)
= ( a ∗ M + b ) ∗ ( p ∗ M + q ) =(a*M+b)*(p*M+q) =(aM+b)(pM+q)
= a ∗ p ∗ M ∗ M + b ∗ p ∗ M + a ∗ q ∗ M + b ∗ q =a*p*M*M+b*p*M+a*q*M+b*q =apMM+bpM+aqM+bq
= 3 2 a ∗ p ∗ M + b ∗ p ∗ M + a ∗ q ∗ M + b ∗ q =\frac 3 2a*p*M+b*p*M+a*q*M+b*q =23apM+bpM+aqM+bq
= M ∗ ( a ∗ p + b ∗ p + a ∗ q ) + b ∗ q + a ∗ p ∗ 1 2 ∗ M =M*(a*p+b*p+a*q)+b*q+a*p*\frac 1 2*M =M(ap+bp+aq)+bq+ap21M

到这一步,其实就差不多了。
那么我们每次递归下去求解上面那些奇怪的东西就好了。
当然,可以暴力求出某些范围内的sg值即可。
这里推荐 ( 0 − 511 , 0 − 511 ) (0-511,0-511) (0511,0511)
时间大概是 O ( l o g 2 x ) O(log^2x) O(log2x)
证明应该是根据它每次做类似于去更号得到的。

回归正题:

回到原来的题目,其实你还是会发现不太会做。
一种暴力的方法:
枚举N!次,把每个独立的游戏都枚举出来。
计算一个向量的sg值其实就相当于取其中两两元素的积,一直这样求下去即可。
然后把这些独立的游戏异或起来即可。

然鹅慢!

考虑优化?
我们发现其中有奇妙的东东:
这玩意儿计算方法其实就是行列式的定义:(虽说百度上面那个定义有点鬼畜,没看懂)

一个n阶行列式中,n个不同行,不同列(编号组成一个排列)的元素的乘积,称为一个项。
行列式的定义:行列式的所有的项乘上(-1)^(排列逆序对个数)的代数和。

那么我们不就可以把上面的定义中乘积定义为NIM积,把和定义为NIM和。
其中那个-1可以不用管它,因为异或的加法逆元是他自己。
大功告成~具体怎么做看下面:
高斯消元求行列式
那么如果算出来的sg值为0,则必败,否则必胜。

然后还有一个小细节:
当求NIM积的逆元时,可以发现其中的费马数是可以用费马小定理来求的。
证明的话,据说其是封闭性的。我太菜了,只能感性理解。

标程

调了好久,最后还T飞了。(原谅我开O3)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cctype>
#define ull unsigned long long
using namespace std;

int n,f[512][512];
ull M[9],a[201][201];

__attribute__((optimize("-O3")))
inline ull read()
{
    ull X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}

__attribute__((optimize("-O3")))
ull find(ull x,ull y)
{
	if (x==0 || y==0) return 0;
	if (x<=511 && y<=511 && f[x][y]!=0) return f[x][y];
	if (x<y) swap(x,y);
	ull m;
	for (register int i=0;i<=5;i++)
	{
		if (x>=M[i]) m=M[i];
		else break;
	}
	ull a=x/m;
	ull b=x & (m-1);
	ull p=y/m;
	ull q=y & (m-1);
	return m*(find(a,p)^find(b,p)^find(a,q))^find(b,q)^find(find(a,p),m/2);
}

__attribute__((optimize("-O3")))
ull qsm(ull a,ull b)
{
	ull t=1;
	ull y=a;
	while (b>0)
	{
		if ((b&1)==1) t=find(t,y);
		y=find(y,y);
		b/=2;
	}
	return t;
}

ull find_ni(ull x)
{
	ull m;
	for (int i=0;i<=5;i++)
	{
		if (x>=M[i]) m=M[i];
		else 
		{
			m=M[i];
			break;
		}
	}
	return qsm(x,m-2);
}

int main()
{
	freopen("data.in","r",stdin);
//	freopen("partition.in","r",stdin);
	freopen("partition.out","w",stdout);
	M[0]=2;
	ull k=1;
	for (int i=1;i<=8;i++)
	{
		k=k*2;M[i]=1;
		for (int j=1;j<=k;j++)
		{
			M[i]=M[i]*(ull)2;
		}
	}
	for (int i=1;i<=511;i++)
	{
		f[1][i]=f[i][1]=i;
	}
	for (int i=2;i<=511;i++)
	{
		for (int j=2;j<=511;j++)
		{
			f[i][j]=find(i,j);
		}
	}
	
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=n;j++)
		{
//			scanf("%llu",&a[i][j]);
			a[i][j]=read();
		}
	} 
	
	for (register int i=1;i<=n;i++)
	{
		bool pd=true;
		int id;
		for (register int j=i;j<=n;j++)
		{
			if (a[j][i]!=0)
			{
				id=j;
				pd=false;
			}
		}
		if (pd==false)
		{
			for (register int j=i;j<=n;j++)
			{
				swap(a[id][j],a[i][j]);
			}
			ull ny=find_ni(a[i][i]);
			for (register int j=i;j<=n;j++)
			{
				a[i][j]=find(a[i][j],ny);
			}
			for (register int j=i+1;j<=n;j++)
			{
				if (a[j][i]!=0)
				{
					ull op=a[j][i];
					for (register int k=i;k<=n;k++)
					{
						a[j][k]=a[j][k]^find(a[i][k],op);
					}
				}
			}
		}
	}
	ull ans=1;
	for (int i=1;i<=n;i++)
	{
		ans=find(ans,a[i][i]);
	}
	if (ans==0) printf("xiaoDwandanle\n");
	else printf("xiaoDyingle\n");
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值