本文首先發布於個人部落格,不定期更新。推薦去我的部落格閱讀,效果更佳。
因為 csp-s 考的太慘了,所以一直想著回頭把前三題都ac了,結果一拖拖到現在才做完(
還有就是動態規劃好難(wtcl)
本題題意較複雜,建議直接去看原題(題目鏈結)
總的來說,合法的括號序列一共分兩類:
包含型:(), (a),(s),(as),(sa)
並列型: ab ,asb
很容易發現合法的括號序列兩端必然分別是左右括號,而且合法的括號序列最小長度為2
資料範圍可知這題大概需要乙個 \(o(n^3)\) 的方法
這道題一看就是區間dp(假如你不知道什麼是區間dp就先去學一下,等以後再看這篇文章吧)
第一直覺估計都是狀態 \(f[i][j]\) 表示 \([i,j]\) 這段區間內的數量,然後轉移方程也都比較好想,例如:
\[\left. \begin
\\ \\\end \right.
\]( \(s[i][j]\) 表示 \([i:j]\) 能否完全由不超過 k 個連續的*
或者?
組成,放在逗號右面表示只有當滿足這個條件時才計算)
(as) 只要列舉一下 s 就行( \(p\) 為 a 的右端點):
\[^f[i][p],\space s[p+1][j-1]=true }
\](sa) 與 (as) 差不多就不再提了。
但是在計算 asb 的貢獻的時候,像我們剛剛那樣設計狀態就會列出錯誤的方程( \(p\) 為 a 的右端點):
\[f[i][j]+=\sum_^\sum_^ f[i][p]\times f[q][j],\space s[p+1][q-1]=true
\]這個式子可以理解成列舉 a 的右端點 \(p\) 和 b 的左端點 \(q\) 來間接列舉 s ,從而轉移 asb,做個特判就可以連 ab 一起處理了。
但是這樣的轉移方程為什麼是錯的呢,我們看下面這樣的括號序列:
()()()
當你在列舉到 a = [1:2] 時會計算一遍這種情況,而 a = [1:4] 的時候又會被計算一遍
於是你發現你算重了。
所以我們要想別的辦法來避免算重。比較容易想到的就是我每次在左邊的 a 只列舉包含型的結構,也就是說在剛剛的例子中列舉 a 只會列舉 [1:2] =()
這種包含型的,而不會列舉 [1:4] =()()
這種並列型的,這樣你每種拆法就會只被計算一次了。
重新設計一下狀態,\(f[i][j]\) 表示該區間內包含型序列的數量,\(g[i][j]\) 表示並列型序列的數量。
重新看一下原來的轉移方程,你會發現 (a),(s) 的轉移方程根本不需要改,(as) 的轉移方程只是把後面的 \(f[i][p]\) 改成 \(f[i][p]+g[i][p]\) 即可
仿照原來的思路,asb 的轉移方程如下:
\[g[i][j] = \sum_^\sum_^ f[i][p] \times (f[q][j]+g[q][j]),\space s[p+1][q-1] = true
\]**如下:(為了清晰直觀,刪去取餘的過程)
for(int p=i+1; p <= j-2; p++)
}
按這個思路寫對了應該就能拿到 65pts 了,現在離 ac 還差乙個優化
效能的瓶頸全都在 asb 的 \(o(n^4)\) 上,所以觀察 asb 的式子,此時 \(i\) 和 \(j\) 都可以當做定值。
後面的 \(f[q][j]+g[q][j]\) 的值與 \(p\) 無關,因此我們可以把 \(f[i][p]\) 從最內層的 \(\sum\) 裡提出來。
所以現在只考慮內層的 \(\sum\):
\[\sum_^(f[q][j]+g[q][j]),\space s[p+1][q-1] = true
\]這時如何優化就很容易想到了:字首和
我們設乙個新陣列 \(h\),先不去考慮*
的條件:
\[h[p]=\sum_^f[t][j]+g[t][j]
\]我們可以在讀入之後預處理出每乙個位置往後延伸的*
或者?
的最長的長度,記為 \(b[i]\)
每次迴圈 \(i\) 的時候預處理 \(h\) 陣列的值,因為 \(l\) 遞增迴圈所以不用擔心 \(f[q][j]\) 還沒有求的問題,這樣我們可以用 \(h[min(j, p+b[p+1]+1)] - h[p]\) 代替原來的內層的 \(\sum\)(其實建議預處理時直接處理出向後延伸的最大下標,應該會方便一點,但我懶得改了)
最終**(**醜,請見諒):
#include #include using namespace std;
const int maxn = 550;
const int mod = 1000000007;
typedef unsigned long long ull;
int inline add(int a, int b)
int n, k;
char a[maxn];
int f[maxn][maxn]; // (...), 後面注釋裡用 f 代替
int g[maxn][maxn]; // (...)...(...) 用 g 代替
bool s[maxn][maxn]; // s[i][j] 表示能否構成連續的而且 個數不超過 k 的 *
int h[maxn], b[maxn];
int main()
b[i] = j-i;
}for(int l=2; l <= n; l++)
if(s[i+1][j-1] && l-2 <= k) f[i][j] = 1; // (s)
f[i][j] = add(f[i][j], add(f[i+1][j-1], g[i+1][j-1])); // (f) 或 (g), 對應題目要求的 (a)
for(int p=i+1; p < j-1; p++)
for(int p=j-1; p > i+1; p--)
// ab 或者 asb
h[i+1] = 0; // 字首和優化
for(int p=i+2; p <= j; p++)
for(int p=i+1; p <= j-2; p++)}}
printf("%d", add(f[1][n], g[1][n]));
return 0;
}
csp s測試41 T2 影子
1 並查集 可以並查集 考慮對點權的限制。嘗試逐點列舉點權,向點權大於等於自己的節點擴充套件,計算最大路徑。優化 瓶頸在於還是有很多重複的。上述的每個節點擴充套件後形成的連通塊點集成為乙個集合,從大點權到小點權只要集合拓展。維護集合 考慮並查集 點權排序,維護集合內最長鏈即可。nlog includ...
Csp S 2020 T4 貪吃蛇 題解
這道題我調了好久 細節非常多。首先,看完題目後,可以發現這是一道博弈問題。那麼接下來,我們假設最強蛇為 x xx 最弱蛇為 y yy 那麼這裡就有兩個結論 如果 x xx 吃了 y yy 不是最弱的蛇,則 x xx 必吃 y yy 證明 假設當前第二強的蛇為 a aa 第二弱的蛇為 b bb 那麼 ...
NOIPD2T2 寶藏 題解
填坑,史前巨坑。題意 對於一張圖,確定乙個點為根,構建乙個生成樹。求代價最小值。代價的定義 樹中每一條邊的權值與較淺點深度的乘積 之和。考場上沒有想清楚就草草碼了乙個prim然後交了,但是因為你代價和深度有關,所以貪心地prim是錯誤的。因為 n 很小,這應當引導我們想到狀壓。套路 答案與深度有關,...