1. 程式人生 > 實用技巧 >多校第十五場 題解

多校第十五場 題解

A. zsy家今天的飯

對於 \(\binom{m}{k}\) 種方案,答案是跨過的邊權*2-直徑。
可以對兩部分分別計算貢獻。

對於前者,可以考慮計算每條邊的貢獻。
若將餐廳點集劃分為 \(a,b\) 兩部分,那麼乘上的係數就是 \(\binom{m}{k}-\binom{a}{k}-\binom{b}{k}\)

對於後者,可以考慮列舉直徑的兩個端點是誰。
有個結論是,樹上到達每個點最遠的點,一定在直徑的端點上。
所以對於其它點,只要判斷與當前列舉端點距離即可。

有個問題是如何處理直徑長度相同,通過討論可以發現只要比較標號就是正確的。
 

B. 劃憤

考慮 \(n=2\) 的情況,其實和 \(nim\)

積的式子是一樣的。
對於 \(n\) 比較大的情況,也就是把所有的單點 \(sg\) 值積在一起。

發現原問題的構造比較奇怪,其實和行列式有點類似。
然而行列式帶了一個 \(-1\) 的係數,然而可以發現異或意義下的 \(-1\) 其實就是不變。
所以只要求行列式就好了。

然而並不會 \(nim\) 積,所以學習了一下。
大概就是解決這樣一個問題: \(sg(x,y)=\text{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\),就有 \(x\otimes y=(a \otimes p \oplus b)\otimes (c \otimes p \oplus d)\),下面用乘法和加法代替 \(nim\) 積和異或。

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

容易發現,這樣的做法會使四個子問題 \(ac,bd,(a+c)(b+d),ac\frac{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}\)
大概的理解就是,這個玩意在 \(\bmod 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\) 來統計這個方案數。
顯然只關注子樹中每個最大深度的聯通塊個數,這個玩意一看就很長鏈剖分。
所以寫個長鏈剖分,對於小於短鏈長度的部分暴力合併,大於的部分打個字尾乘法標記就行了。