2024.10 做題記錄 /

Hypoxia571發表於2024-10-06

CF2004E

套用 SG 函式的結論,我們先打單個遊戲的表再異或即可得到答案。首先對於一個大小為 \(i\) 的堆有 \(SG[i]=\text{mex}_{j\bot i}\{SG[i-j]\}\),容易暴力 dp。

int SG[N];
int f(int x) {
	if(SG[x]!=-1) return SG[x];
	if(x==0) return SG[0]=0;
	vector<int> g;
	up(i,1,x) if(__gcd(i,x)==1) g.pb(f(x-i));
	sort(g.begin(),g.end());
	if(g[0]>0) return 0;
	up(i,0,(int)g.size()-2) if(g[i]+1!=g[i+1]&&g[i]!=g[i+1]) return g[i]+1;
	return g[g.size()-1]+1; 
}

首先 \(SG[0]=0,SG[1]=1\),然後第 \(i\) 個質數的 SG 值是 \(i\) 強相關的,合數的 SG 是最小質因子的 SG,線性篩求出即可。


CF2005E1/2

E1 的話直接按照博弈論的東西暴力 dp 即可,是 \(O(lnm)\) 的,具體而言對於一個位置考慮其子矩陣中代表這一輪的格子,如果不能到達必勝態就是必敗,暴力 dp 即可,\(f[i][j][k]\) 表示 \((i,j)\) 往下的矩陣中第 \(k\) 輪的狀態,注意定義是從 \((i,j)\) 走而不是走到 \((i,j)\),容易倒序 dp。

現在就要最佳化 dp 了,這個轉移本身形式感覺沒啥簡便最佳化,找找題目性質,注意到如果一次轉移可以走到多個點 \(x=p_1,\dots,p_x\),一定走一個右下方沒有 \(x\) 的位置,因為如果走到下面都輸那走上面一定會輸但是走下面會贏的走上面不一定會贏(下一手選擇被包含了),那有用的點按照橫/縱座標排序之後縱/橫座標會有單調性,這代表了按照橫/縱座標排序之後任意矩陣能覆蓋到的合法點集是一個區間。那直接對每一輪記一下合法位置,對 \(f\) 做一個字首和,查詢的時候二分之後差分,判斷一個子矩陣有沒有勝態即可,複雜度 \(O(nm\log nm)\)


CF2005D

經典結論:序列上固定一個左端點之後向右擴充右端點,本質不同的 \(\gcd\) 至多隻有 \(\log\) 種。因為後面的 \(\gcd\) 一定是前面的 \(\gcd\) 的因數,每次變化至少使得值 \(\times\frac{1}{2}\)

對於每一個點預處理出每一個作為左端點之後序列上的 \(\gcd\) 的段,一個段放在一起考慮,顯然是拿最右邊的不劣,因為左邊的貢獻一樣的話,右邊留下的數少更容易讓後面部分的 \(\gcd\) 變大,設 \(L_{a/b}[i]\) 表示序列 \(a/b\)\(i\) 個數作為左端點的 \(\gcd\) 段的右端點集合,預處理可以遞推,求答案暴力列舉即可,複雜度控制在幾隻 \(\log\) 呢。


CF2002E

按照連續的顏色的段考慮,在刪出空段前是頻繁的每次去頭,刪除空段之後如果該段左右段顏色相同則會導致合併,感覺容易拿資料結構維護。

維護當前刪除減量 \(\Delta\),用 stl set(不熟練 set 可以寫帶刪除標記的 priority_queue)維護二元組 \((x,y)\) 表示段 \(x\) 的長度是 \(y+\Delta\),以 \(y\) 為關鍵字排序。每次找出最先被刪空的那一段,向前推進時間,更新一下用 stl map 維護一個前後指標即可。


CF2003 E2

首先先考慮往序列裡面投一個數會得到什麼,設序列中第一/二個沒有出現的數為 \(L,R\),則投機 \(L\)\(R\),否則得到 \(L\)

先形式化描述一下,首先 \(L\to L\) 這種當成沒有發生就行了,所以是我們可以選擇 \(?\to R\) 或者 \(L\to R\)。礙於兩種移動相互限制,先想辦法找點性質,感覺第一類操作這個憑空變出某個數只用一次夠了,那隻連 \(L\to R\) 的邊,可以發現因為只有小往大所以是 DAG 這很方便,設 \(f_i\) 表示從 \(i\) 走能到達的最大的點是什麼,按 topu 序倒序 dp 即可。具體而言度數 \(>1\) 的點和 \(x\) 可以作為根,然後任意 \(L\) 可以作為答案,然後就是求出從任意根開始走能走到的最大點。