暑假模擬賽總結

ccjjxx發表於2024-07-12

csp-j模擬賽2

A 公式求值

加入字首和思想的高精度加法。

B 最長的Y

我永遠喜歡 IOI 賽制。

考場寫了兩份程式碼,調了兩個小時,結果到最後 10 分鐘發現第一個程式碼能夠 subtask 1,第二個能過 subtask 2,於是結合起來喜提 \(60\) 分。

我們先找到每一個 \(Y\) 塊,然後迴圈找到左右兩邊離他最近的 \(Y\),更新塊長度和答案,直到耗盡 \(k\),對於每個塊更新答案即可。

C 交換序列

四維 dp??

D 最長路徑

三維 dp??

csp-j模擬賽3

A 分糖果

氣死人了,沒仔細檢查結果全 WAAAAAA

要把兩種情況分類討論,同時計算取最大值。

B 乒乓球

這是一個很巧妙的找規律,考慮到超過 \(11\) 分的比分性質相同,可以直接把類似於 \(114:115\) 當成 \(10:11\),這樣規律就比原始的比分更好找了。

C 與或

直接狀壓暴力。

簡單來說,觀察到 \(%30\) 資料的 \(n,k \le 20\),而且 \(2^{20}=1048576‬\) 我們便可以想到把 \(\&\)\(|\) 的狀態壓縮成一個數。

因題目要求 \(|\) 的字典序小於 \(\&\),那麼可以令這一位的 \(|\) 表示 \(0\)\(\&\) 表示 \(1\),那麼序列 \(\& \& |\& \& \& \& \& \& |\& |\& ||\&|=11011111101010010=114514‬\)

在新的位置新增一個 \(|\) 就是 \(S<<1\),新增一個 \(\&\) 就是 \(S<<1|1\)

最後在更新答案的時候可以直接比較數字的大小而不是遍歷全部陣列。

這樣能讓暴力更快一點(指快一兩毫秒)

D 跳舞

要預處理每兩個數的 \(\gcd\),時間複雜度 \(O(n^2 \log n)\),然後就是類似與區間 dp 的東西了。

E 音樂播放器

機率 dp。

csp-j模擬賽4

A 超市搶購

這就是小孩題,但因為隨機數生成器的問題卡了 \(\text{long long}\)

首先題目給出的隨機數生成器是這樣的:

int randseed;
unsigned int rnd()
{
  unsigned int r;
  r = randseed = randseed * 1103515245 + 12345;
  return (r << 16) | ((r >> 16) & 0xFFFF);
}
  • 首先觀察到這個子函式的型別的 \(\text{unsigned int}\)\(32\) 位無符號整型,即最高一位代表數字而非符號。

這樣是為了防止溢位成負數,也是方便設定生成數字的位數為恰好 \(32\)

但是我們觀察到一個細節,就是 \(randseed\) 這個變數是 \(\text{int}\) 型別而非 \(\text{unsigned}\),並且迴圈中輸出的 \(randseed\) 也大多是負數。

原因很簡單,\(\text{int}\)\(\text{unsigned int}\) 儲存的形式是相同的,沒開 \(\text{unsigned int}\) 可能單純是不想,但是我們 \(r\) 考慮的就要多了,所以必須得是 \(\text{unsigned}\)


  • 然後,觀察到這個式子:

r = randseed = randseed * 1103515245 + 12345;

這是很古老的一種隨機數生成演算法,一種最簡單的單狀態 LCG(線性同餘生成器),至於是啥,咱也不知道。

其中用到了 \(1103515245\)\(12345\) 這兩個數,我在網上找到的解釋是

事實證明,rand() 的每個連續輸出的低位將交替(例如,0,1,0,1,0,1,...)。

你明白為什麼嗎? x * 1103515245 的低位與x的低位相同,然後加 12345 只會翻轉低位。因此,低位交替


  • 最後看到 return

\(0xFFFF=65535=2^{16}-1\),並且 \((r << 16) | ((r >> 16) \& 0xFFFF)\) 使得或運算左邊的表示式的低 \(16\) 位都為 \(0\),或運算右邊的表示式只有最低的 \(16\) 位。

這樣與起來的長度必定大於等於 \(32\) 位,關鍵來了。

如果返回值是 \(\text{unsigned long long}\),那麼 r<<16 是會超過 \(\text{int}\) 的,因為不能保證 \(r\) 的值必定小於 \(65536\)

如果返回值是 \(\text{unsigned int}\),那麼會自動溢位,直接將高位捨棄,保留低位。

這樣就是不能用 \(\text{long long}\) 的原因了。


但事實上,那個 &0xFFFF 的運算是無意義的,因為你的 \(r\) 最大也是 \(32\)\(1\),右移 \(16\) 位必定小於 \(65535\) 了。

但事實上,如果把 &0xFFFF 放在或運算的左邊,也就是這樣寫,可以使得返回值嚴格在 \(\text{unsigend int}\) 中:

(r & 0xFFFF) << 16 | (r >> 16)

這樣會先使 \(r\) 只有最多 \(16\) 位,再右移 \(16\) 位那肯定小於 \(\text{unsigned int}\) 的最大值而不會溢位了。

但事實上,這樣修改隨機數生成器,還是保留 #define int long long 也是不行的,

原因很簡單,\(r\) 在和隨機數種子生成隨機數的時候就可能已經溢位了,要想完美解決,這樣寫就好了:

B 核酸檢測

考場上推出 \(O(NM)\) 的 dp 了,可惜死活沒想到要把區間按左端點排個序,沒想到和瑋的思路一模一樣,稍稍最佳化一下就 A 了、

首先,考慮狀態 \(dp[i][j]\) 表示把前 \(i\) 個人都喊下來且最後一次喊在 \(k\) 時刻的最小次數,\(g[i][j]\) 表示其方案數。

容易得到:

  • 當前掃描到第 \(i\) 個人,在第 \(j\) 時刻時,如果 \(dp[i-1][j]\) 有值,那麼相當於和上一個人情況相同。

\[dp[i][j]=dp[i-1][j] \]

  • 否則,可以把從 \(1\)\(i\) 所有人的區間看成兩個部分:前 \(i-1\) 和第 \(i\),容易得到:

\[dp[i][j]=\min(dp[i-1]+1) \]

對於 \(g[i][j]\),和上面類似:

  • 當前掃描到第 \(i\) 個人,在第 \(j\) 時刻時,如果 \(dp[i-1][j]\) 有值,那麼相當於和上一個人情況相同。

\[g[i][j]=g[i-1][j] \]

  • 否則,可以把從 \(1\)\(i\) 所有人的區間看成兩個部分:前 \(i-1\) 和第 \(i\),每一種都能從可轉移的位置得到,容易得到:

\[g[i][j]=\sum (g[i-1][k]\times [dp[i][j]=dp[i-1][k]+1]),k\in [l[i-1],r[i-1]] \]

最佳化一

有這樣一張圖:

image

可以看到 \(dp[i][j]\) 的值無非就是那兩種,所以我們可以在處理上一行的同時直接處理出 \(\min(dp[i-1][j])\)

這樣,查詢最小方案的時間複雜度就來到了 \(O(NM)\)

最佳化二

和上一最佳化一樣,\(g[i][j]\) 的值也只有兩種,因為,如果 \(g[i-1][j]\) 有值,那麼 \(dp[i-1][j]\) 也必有值,也就是圖中左邊的情況。

否則,\(g[i-1][j]\) 沒有值,\(dp[i-1][j]\) 也必定沒有值,對應圖中右邊情況,這時的 \(dp[i][j]=\min(dp[i-1])\),我們可以發現一個關鍵:

如果 \(g[i-1][j]\) 無值,那麼\(g[i][j]\) 所對應的所有 \(dp[i-1]\) 都一樣 \(=\min(dp[i-1])\)

我們可以 \(O(M)\) 地在每次計算完後處理出所有滿足 \(dp[i-1][j]=\min(dp[i-1])\)\(g[i-1][j]\) 之和。

因為這種情況一定在上一個區間的右端外並且不重合,所以這種情況的 \(g[i][j]\) 一定相等,等於我們預處理出的 \(sum\)

這樣,我們也實現了 \(O(NM)\) 的查詢方案組數,總體時間複雜度來到了可喜的 \(O(NM)\)

最佳化三

雖然但是,時間複雜度剛好能過,但空間複雜度開始爆炸。

觀察我上面寫的所有式子,對於第 \(i\) 個人,他的所有答案、貢獻只和 \(i-1\) 個人有關,那還怕啥,直接滾!

滾完之後就 ok 了

實際得分是 \(100\) 分,研究了一下,發現彙總答案確實需要第 \(n+1\) 個人,然後 第一個輸出 \(ans-1\)

C 七龍珠

不太會啊,看了題解也沒思路。

考場推 \(k=1\) 情況的部分分,結果寄了。。

D 龍珠遊戲

dfs 寫假了,急急急。

image

csp-j模擬賽5

A 算術求值

媽呀,想複雜了,但是時間複雜度很優秀啊 hhhhhhh

考場上因為取模一直卡在 \(90\) 分,硬控半個小時。寫到 T2 突然迴光返照想起來補了取模就過了。。

考慮這樣一個事情:這樣的一個算式其實等同於一個線性的週期函式,週期為 \(T=998244353\),定義域為 \((-\infin,+\infin)\),值域為 \([0,998244352]\)

但實際上的定義域,是 \([1,n]\)。我們需要在閉區間 \([1,n]\) 上找到 \(f(x)=kx+b\) 的極大值,這裡的函式 \(f(x)\) 可以直接透過題目輸入轉化出來,很容易實現。

我們會遇到兩種情況:

  1. 定義域橫跨兩個週期
  2. 定義域在一個週期內

第一種就是題目樣例 \(x-6\) 的情況,在 \(f(5)\) 處能取到原函式的極大值。

我們可以這樣判斷:

首先令 \(f(x)_{\max}=m\)

注意這個 \(m\) 是一個取值的集合,我們這樣表示:\(m=998244352+k998244353,k\in \Z\)

回到剛才的函式,我們迴圈 \(m\) 的取值,並令

\(kx+b=m\)

\(x=\frac{m-b}{k}\)

每得到一個 \(x\) 就和定義域 \([1,n]\) 比較,如果在定義域內就更新最大值,準確來說是 \(f([\frac{m-b}{k}])\)


如果當前的 \(x\) 已經超出定義域,那麼我們可以斷定:定義域內不存在 \(f(x)\) 的最大值

這就是定義域被包含在一個區間內的情況,因為 \(f(x)\) 在每個區間單調遞增,所以這個情況的最大值就是在 \(f(n)\) 處取得。

這樣就把所有情況討論盡了。

\(k\)\(b\) 的時間複雜度是近似 \(O(s)\),找答案是時間複雜度基本上 \(O(1)\),不會跑幾次。

B 括號序列

直接棧+貪心 \(O(n)\) 戰神一遍過。

C Count Multiset

神秘 dp,我不會,考場上還是普普通通的暴力 \(10\) 分。

D 選數

據說標程的時間複雜度已證偽,太嚇人了,我選擇 \(O(n^2 \log n)\) 暴力+二分答案

E 劃分序列

dp+二分答案區間,老套路看來得好好回顧一下。

csp-s模擬賽1

A 最短路 (path)

正解是 floyd,稍稍修改一下就能過,考場上沒看出來,硬是打了一個 \(dfs\) 加樹剖過了 \(30\)……

B 方格取數(matrix)

所有暴力終將會被繩之以法!

\(n^2\) 過百萬,暴力碾標算

正解是單調棧/懸線法複雜度 \(O(n^2)\),但是昨天晚上也是把 \(O(n^4)\) 程式碼卡過去了。

晚上回去想了一下,最後一個大點怎麼會是 \(-1\),而且 \(-1\) 的情況有哪些,昨天考場上想出來的都是能預處理的,現在我總結一下:

  1. 矩陣所有元素都大於 \(2k\)

可以預處理提前計算。

  1. 矩陣所有元素之和小於 \(k\)

同樣字首和一下就能判斷。

  1. \(k=2\) 且矩陣中所有 \(1\) 的上下左右沒有 \(1\)(是在矩陣沒有單另的 \(2,3,4\) 前提下)

輸入的時候可以預處理

  1. 其他情況

這裡主要說的就是這個其他情況,因為前面說的 \(4\)\(-1\) 都可預處理,只有這個不行。

考慮這樣一件事:如果矩陣中的元素有 \(n^2-1\) 個都是 \(>2k\),只有一個 \(<k\),那麼它肯定不符合,並且要得出答案必然要遍歷全部矩陣,跑滿 \(O(n^4)\)

或者這種情況,\(2k+1\)\(k-1\) 交替出現,也是上述情況。

5 3
2 7 2 7 2
7 2 7 2 7
2 7 2 7 2
7 2 7 2 7
2 7 2 7 2

可以透過資料生成器來做出這種資料的完全體:

#include<bits/stdc++.h>
using namespace std;
int n,k;
int main()
{
	freopen("in.txt","w",stdout);
	srand(time(NULL));
	n=2000,k=10000000;
	cout<<n<<" "<<k<<endl;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if((i%2==1&&j%2==1)||(i%2==0&&j%2==0)) cout<<k-1<<" ";
			else cout<<2*k+1<<" "; 
		}
		cout<<endl;
	}
}

但是又會想到,遍歷全部矩陣的過程是一個嚴謹的逼近答案的過程,當迴圈次數趨近於無窮大而沒有合適的子矩陣時,答案是 \(-1\) 的機率也會趨近於 \(1\),所以我們在迴圈中加入卡時程式碼是不無道理的 ,嘿嘿

那麼能完全 hack 的資料應該把答案分佈在最右下角,並且有唯一答案,這樣也是會跑滿的。

C 陣列(array)

拿兩個線段樹分別維護區間乘和區間取模,統計每個數的質因數分佈,用 \(\text{long long}\) 狀壓維護一下。

考場上一直沒想到用尤拉函式定義式去算,一直想著線性篩。。。

D 樹(tree)

服了,勾式細節,考場上都把 \(40\) 程式碼碼出來了,結果細節不到位,每個點都差一點,細節啊細節。。

正解是長鏈剖分+根號分治,比較神秘,我看一個人寫的序列分塊+樹剖的程式碼還挺好。