noi前第十五场 题解

A. zsy家今天的饭

对于 (inom{m}{k}) 种方案,答案是跨过的边权*2-直径。
可以对两部分分别计算贡献。

对于前者,可以考虑计算每条边的贡献。
若将餐厅点集划分为 (a,b) 两部分,那么乘上的系数就是 (inom{m}{k}-inom{a}{k}-inom{b}{k})

对于后者,可以考虑枚举直径的两个端点是谁。
有个结论是,树上到达每个点最远的点,一定在直径的端点上。
所以对于其它点,只要判断与当前枚举端点距离即可。

有个问题是如何处理直径长度相同,通过讨论可以发现只要比较标号就是正确的。
 

B. 划愤

考虑 (n=2) 的情况,其实和 (nim) 积的式子是一样的。
对于 (n) 比较大的情况,也就是把所有的单点 (sg) 值积在一起。

发现原问题的构造比较奇怪,其实和行列式有点类似。
然而行列式带了一个 (-1) 的系数,然而可以发现异或意义下的 (-1) 其实就是不变。
所以只要求行列式就好了。

然而并不会 (nim) 积,所以学习了一下。
大概就是解决这样一个问题: (sg(x,y)= ext{mex}_{i<x or j<y}(sg(i,j)))
然后可以发现这样一些性质:
1.对于 (y=2^{2^x},z < y),有 (y otimes z=yz)
2.对于 (y=2^{2^x}),有 (y otimes y=frac{3}{2}y)
3.(otimes) 满足交换律、结合律。
4.(0 otimes x=0,1 otimes x=x)

这样的话,要求 (x otimes y),可以先找出最大的 (p=2^{2^x},p leq max{x,y})
(x=ap+b,y=cp+d),就有 (xotimes y=(a otimes p oplus b)otimes (c otimes p oplus d)),下面用乘法和加法代替 (nim) 积和异或。

[egin{aligned} xy&=(ap+b)(cp+d)\ &=acp^2+bd+bcp+adp\ &=ac(p+frac{1}{2}p)+bd+(bc+ad)p\ &=acp+acfrac{1}{2}p+bd+((a+b)(c+d)-ac-bd)p\ end{aligned} ]

容易发现,这样的做法会使四个子问题 (ac,bd,(a+c)(b+d),acfrac{1}{2}p) 规模都达到开根的效果。
这样递归的层数就是 (log log V),所以复杂度是 (4^{log log V}=(2^{log log V})^2=log^2V)
可以预处理 (x,y<256),就跑的很快了。

然后为了能够进行高斯消元,需要处理逆元。
做法大概是,找到任意一个大于 (y)(2^{2^x}),那么 (y^{-1}=y^{2^{2^x}-2})
大概的理解就是,这个玩意在 (mod 2^{2^x}) 意义下是封闭的,所以满足费马小定理的样子。

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
int n;
ull ret[257][257],A[155][155];
inline ull mul(ull x,ull y,int len=32){
	if(x<=1||y<=1) return x*y;
	if(len<=4&&ret[x][y]) return ret[x][y];
	ull a=x>>len,b=x^(a<<len),c=y>>len,d=y^(c<<len),A=mul(a,c,len>>1),B=mul(b,d,len>>1),C=mul(a^b,c^d,len>>1),D=mul(A,1ull<<len-1,len>>1),r=D^B^((C^B)<<len);
	return len<=4?ret[x][y]=r:r;
}
inline ull getinv(ull x,ull y=2,ull r=1){
	while(y&&y<=x) y*=y;
	for(y-=2;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
	return r;
}
inline bool gauss(){
	for(int i=1,j;i<=n;++i){
		for(j=i;j<=n&&!A[j][i];++j); if(j>n) return 0;
		swap(A[i],A[j]); ull c=getinv(A[i][i]);
		for(j=i;j<=n;++j) A[i][j]=mul(A[i][j],c);
		for(j=i+1;j<=n;++j) if(A[j][i]) for(int k=n;k>=i;--k) A[j][k]^=mul(A[j][i],A[i][k]);
	}
	return 1;
}
int main(){
	freopen("sui.in","r",stdin);
	freopen("sui.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) scanf("%llu",&A[i][j]);
	return puts(gauss()?"xiaoDyingle":"xiaoDwandanle"),0;
}

 

C. 树上的鼠

有个结论是,先手必败与 (1) 号点在直径的中点等价。

原因大概是这样的,(1) 号点在中点的话,就可以以 (1) 号点为根建树出来。
对于先手的每个操作,后手都可以移动到不同子树的相同深度的点,所以先手必败。
如果 (1) 号点不在中点,那么先手可以直接移动到中点,即先后手互换,所以这个结论是正确的。

所以可以直接用 (dp) 来统计这个方案数。
显然只关注子树中每个最大深度的联通块个数,这个玩意一看就很长链剖分。
所以写个长链剖分,对于小于短链长度的部分暴力合并,大于的部分打个后缀乘法标记就行了。

原文地址:https://www.cnblogs.com/skyh/p/13399029.html