CSP S 2019 Emiya 家今天的飯

2022-04-29 06:06:08 字數 3669 閱讀 4765

類似 烏龜棋 的思想,由於 \(64pts\) 的 \(m <= 3\),非常小

我們可以設乙個 \(dp\),建立 \(m\) 個維度存下每種物品選了幾次:

狀態轉移:根據題意,每種烹飪方法最多選一道菜。

答案:\(\sum_^\sum_^\sum_^f[n][a][b][c]\ (max(a, b, c) <= \lfloor(a + b + c) / 2\rfloor 且 a + b + c > 0)\)

小優化:發現所有狀態只會從$a, b, c <= $ 自己的轉移,所以可以用類似揹包優化空間的思想,從大到小列舉狀態,第一維可以滾動掉。

最多選 \(n\) 道菜故時間複雜度 \(o(n^)\)

#include #include using namespace std;

const int n = 45, m = 6, p = 998244353;

int n, m, a[n][m];

typedef long long ll;

int f[n][n][n];

int main() }}

}int ans = 0;

for (int a = n; ~a; a--) }}

printf("%d\n", ans);

return 0;

}

發現 \(64pts\) 後的 \(m\) 猛增,所以我們的演算法一定不能具體記錄每種主要食材選了多少了。

我們發現乙個方案不合法,有且只會有乙個主要食材 $ > $ 總數的一半,所以我們不妨考慮容斥,用所有方案數量 - 不合法數量。

所有方案數量很好求,做乙個分組揹包即可:

\(f[i][j]\) 表示前 \(i\) 種烹飪方式,做了 \(j\) 道菜的方案數。

狀態轉移:

所有方案數量 $ = \sum_^f[n][j]$

優化\(i, j\) 以來比它小的 \(i', j'\),第一維滾動掉

觀察第二種放菜的轉移:\(f[i - 1][j - 1] * a_ + f[i - 1][j - 1] * a_ +...+ f[i - 1][j - 1] * a_ = f[i - 1][j - 1] * (a_ + a_ + ... + a_)\)。我們可以 \(o(nm)\) 預處理 \(s_i = a_ + a_ + ... + a_\)。每個狀態即可 \(o(1)\) 轉移。

這步的時間複雜度

這步有 \(n ^ 2\) 個狀態,\(o(1)\) 轉移。 所以時間複雜度 \(o(n ^ 2)\)

由於剛才我們發現的性質:所有不合法方案中有且只會有乙個主要食材 $ > $ 總數的一半,我們稱那個主要食材為越界食材,我們設越界食材為 \(c\)。

所以我們不妨先用 \(o(m)\) 列舉 \(c\) 。

那麼我們可以把其他食材歸結為符合條件的食材,我們便可以用乙個維度來記錄它選了多少啦~

設 \(dp[i][j][k]\) 為前 \(i\) 種烹飪方式,第 \(c\) 種(越界食材)選了 \(j\) 道,其他食材選了 \(k\) 道的方案數。

狀態轉移:\(\sum f[n][j][k] (j > k)\)

優化:跟之前一樣可以滾動掉第一維

第三種轉移最耗時,考慮用求解所有方案數量優化2的思想:\(\sum_^dp[i - 1][j][k - 1] * a_ = dp[i - 1][j][k - 1] * (\sum_^a_) = dp[i - 1][j][k - 1] * (s_i - a_)\) 我們就做到了 \(o(1)\) 轉移。

這步的時間複雜度

\(o(m)\) 列舉越界食材後,做乙個 \(o(n ^ 3)\) 的 \(dp\)。求解不合法數量的總時間複雜度為 \(o(n ^ 3m)\)。

總時間複雜度:\(o(n ^ 3m)\)

#include #include #include using namespace std;

typedef long long ll;

const int n = 105, m = 2005, p = 998244353;

int n, m, a[n][m], f[n], s[n];

int dp[n][n];

/*dp[i][j] 表示不合法的選了 i 個,剩下的總共選了 j 個的方案數

*/ll ans = 0;

/*f[i] 表示做了 i 道菜的方案數

*/void inline add(int &x, ll y)

int main()

f[0] = 1;

for (int i = 1; i <= n; i++)

for (int j = i; j; j--)

add(f[j], (ll)f[j - 1] * s[i]);

for (int i = 1; i <= n; i++) ans = (ans + f[i]) % p;

for (int c = 1; c <= m; c++) }}

for (int j = 1; j <= n; j++)

}printf("%lld\n", ans);

return 0;

}

延續 \(84pts\) 的思想,求解不合法數量的 \(o(n ^ 3m)\) 拖累了我們,我們考慮優化。

我們不關係具體越界食材與其他食材選了多少。只用保證越界食材數 $ > $ 其他食材數數即為不合法狀態。

不妨把這兩個的差作為乙個維度,這樣即可讓 \(dp\) 狀態降一維:

狀態轉移:

答案貢獻:

\(\sum dp[n][j] (j > 0)\)

總時間複雜度 \(o(n ^ 2m)\) 完美通過本題。

\(tips\):

注意做差有可能為負數,我們可以把所有狀態加乙個 \(+n\) 的偏移量就不會陣列越界了。

不要忘記取模!!

#include #include #include using namespace std;

typedef long long ll;

const int n = 105, m = 2005, p = 998244353;

int n, m, a[n][m], f[n], s[n];

int dp[n][n << 1];

/*dp[i][j] 表示不合法的選了 i 個,剩下的總共選了 j 個的方案數

*/ll ans = 0;

/*f[i] 表示做了 i 道菜的方案數

*/void inline add(int &x, ll y)

int main()

f[0] = 1;

for (int i = 1; i <= n; i++)

for (int j = i; j; j--)

add(f[j], (ll)f[j - 1] * s[i]);

for (int i = 1; i <= n; i++) ans = (ans + f[i]) % p;

for (int c = 1; c <= m; c++)

}for (int j = n + 1; j <= n * 2; j++) ans = (ans - dp[n][j] + p) % p;

}printf("%lld\n", ans);

return 0;

}

CSP S 2019 Emiya 家今天的飯

loj 3211 看到題目中要求每種主要食材至多在一半的菜中被使用,容易想到補集轉換。即 ans 總方案數 存在某一種食材在一半以上的菜中被使用的方案。總方案數很容易求 即對於每一種烹飪方法選至多一道菜的方案為 s i 1 其中 s i sum a 故總方案數 prod s i 1 1 其中 1是因...

CSP S2019 Emiya 家今天的飯 題解

csp s2 2019 d2t1 很不錯的一題dp,通過這道題學到了很多。身為乙個對dp一竅不通的蒟蒻,在考場上還掙扎了1h來推式子,居然還有幾次幾乎推出正解,然而最後還是只能打個32分的暴搜滾粗 題意分析 給出乙個矩陣,要求每行只能選乙個節點,每列選的節點不能超過所有選的節點的一半,不能不選,給出...

csp2019 Emiya家今天的飯

作為提高組d2t 1d2t1 d2t1 比去年難 所以這道題我打的特別的差 這道題我們很顯然可以看到可以打乙個暴力 複雜度o n n o n n o n n 我考場上就達到了這裡 我太菜了 void dfs int u,ll plus dfs u 1,plus rep i,1 m if a u i ...