AtCoder abc256全题解(区间合并模板、矩阵快速幂优化dp、线段树……)
文章目錄
- A
- B
- C-枚舉
- D-區間合并模板
- E-圖論建模,函數圖的性質
- 題意
- 思路
- 代碼
- F-樹狀數組
- 題意
- 思路
- 代碼
- G-矩陣快速冪優化dp
- H-線段樹
- 思路
- 實現
傳送門
本文CSDN
本文juejin
作者:hans774882968以及hans774882968
A
水,略。
B
模擬即可。
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> pii; #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define re_(i,a,b) for(int i = (a);i < (b);++i) #define dwn(i,a,b) for(int i = (a);i >= (b);--i)const int N = 5 + 5;int n, a[N];void dbg() {puts (""); } template<typename T, typename... R>void dbg (const T &f, const R &... r) {cout << f << " ";dbg (r...); } template<typename Type>inline void read (Type &xx) {Type f = 1;char ch;xx = 0;for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';xx *= f; } void read() {} template<typename T, typename ...R>void read (T &x, R &...r) {read (x);read (r...); }int main() {read (n);int ans = 0;rep (_, 1, n) {int x;read (x);a[0]++;dwn (i, 3, 0) {if (i + x < 4) a[i + x] += a[i];else ans += a[i];a[i] = 0;}}printf ("%d\n", ans);return 0; }C-枚舉
題意:有一個九宮格,每格填一個正整數,輸入3 <= h[0~2] <= 30, 3 <= w[0~2] <= 30分別表示期望的每行和每列的數字的和。求方案數。
只需要枚舉4個格子就能確定所有的數了,計算量30^4完全可行。我選擇的是(0,0), (0,1), (1,0), (1,1)這4個格子。
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> pii; #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define re_(i,a,b) for(int i = (a);i < (b);++i) #define dwn(i,a,b) for(int i = (a);i >= (b);--i)const int N = 2e5 + 5;int n, w[5], h[5];void dbg() {puts (""); } template<typename T, typename... R>void dbg (const T &f, const R &... r) {cout << f << " ";dbg (r...); } template<typename Type>inline void read (Type &xx) {Type f = 1;char ch;xx = 0;for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';xx *= f; } void read() {} template<typename T, typename ...R>void read (T &x, R &...r) {read (x);read (r...); }int main() {n = 3;re_ (i, 0, n) read (w[i]);re_ (i, 0, n) read (h[i]);int ans = 0;re_ (i0, 1, min (w[0], h[0]) - 1) {re_ (i1, 1, min (w[1], h[0]) - 1) {re_ (i3, 1, min (w[0], h[1]) - 1) {re_ (i4, 1, min (w[1], h[1]) - 1) {int i2 = h[0] - i0 - i1, i5 = h[1] - i3 - i4, i6 = w[0] - i0 - i3, i7 = w[1] - i1 - i4;if (i2 > 0 && i5 > 0 && i6 > 0 && i7 > 0) {int i81 = h[2] - i6 - i7, i82 = w[2] - i2 - i5;if (i81 == i82 && i81 > 0) ++ans;}}}}}printf ("%d\n", ans);return 0; }D-區間合并模板
區間合并模板,參考:https://blog.nowcoder.net/n/834a656e47df44e58e830fdd87d3e253。
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> pii; #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define re_(i,a,b) for(int i = (a);i < (b);++i) #define dwn(i,a,b) for(int i = (a);i >= (b);--i)const int N = 2e5 + 5;int n; pii a[N];void dbg() {puts (""); } template<typename T, typename... R>void dbg (const T &f, const R &... r) {cout << f << " ";dbg (r...); } template<typename Type>inline void read (Type &xx) {Type f = 1;char ch;xx = 0;for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';xx *= f; } void read() {} template<typename T, typename ...R>void read (T &x, R &...r) {read (x);read (r...); }int main() {read (n);rep (i, 1, n) read (a[i].first, a[i].second);sort (a + 1, a + n + 1);vector<pii> ans;for (int i = 1; i <= n; ++i) {int l = a[i].first, r = a[i].second;while (i <= n && a[i + 1].first <= r) r = max (r, a[++i].second);ans.push_back ({l, r});}for (pii v : ans) printf ("%d %d\n", v.first, v.second);return 0; }E-圖論建模,函數圖的性質
題意
n <= 2e5個人排隊拿糖果。輸入長為n的兩個數組x, c,表示如果i號人排在x[i]號人后面,則i號人會受到c[i]的傷害。請你求一個排列,使所有人受到的總傷害最小。保證i ≠ x[i]。
思路
嘗試往逆序對、二分、貪心和自定義排序等方面想,一無所獲。只好看答案,沒想到是圖論題!
這個圖有n條有向邊,i -> x[i],官方題解指出這種圖叫做functional graph,顧名思義,以i為自變量,x[i]為因變量,確實符合函數定義。函數圖有如下特殊性質:它的每個無向圖意義下的連通分量恰好有一個環。官方題解用數學歸納法,用了不少篇幅來證明。其實感性上更好理解:每個點都必須有一個出度,而DAG拓撲序最大的點出度為0,故必定有環。而環套環要求有些點出度至少為2。
這樣每個連通分量就分為非環的部分和環上的部分。對于非環的部分,直接定順序為拓撲序則傷害為0,最優。環上的部分至少有一個人會受傷害,那么我們只讓傷害值最小的人受傷(排最后)即可,貢獻min(c[y1],c[y2],...)。
代碼
F-樹狀數組
題意
給你長為n <= 2e5的數組。有兩種操作:
思路
套路題。首先考慮把它寫成只與a[1], a[2], ..., a[x]有關的形式。對于二階前綴和,用貢獻思想很容易發現是C[x] = x*a[1]+(x-1)*a[2]+...+1*a[x],所以D[x] = (x+2-1)*(x+1-1)/2*a[1]+(x+2-2)*(x+1-2)/2*a[1]+...+(x+2-x)*(x+1-x)/2*a[x]。
對于(x+2-i)*(x+1-i)/2*a[i],我們希望x的函數與和i有關的函數分離,則x的函數是系數,和i有關的函數是需要維護的數組,這樣題目就做完了。分離出(x+2)*(x+1)/2 * a[i] - (2*x+3)/2 * (i*a[i]) + 1/2 * (i*i*a[i])。因此只需要維護a[i], i*a[i], i*i*a[i]的前綴和。
拓展:k階前綴和同理可做。但k較大的時候是否能寫代碼來求多項式系數呢?我暫時做不到。
代碼
- 注意一切皆可模法。
G-矩陣快速冪優化dp
太難了,只能想到2^D的做法(枚舉頂點顏色的狀態位),直接學習題解了……
定義dp[i][S = 0~3]為填了前i條邊,第一個頂點和當前的邊的頂點的顏色分別為狀態位的高位和低位的方案數,1表示白色(1表示黑色同理可做)。顯然第n條邊的頂點和第一個頂點顏色要相同,所以答案dp[n][0] + dp[n][3]。
但我們發現沒法轉移,因為不知道之前的白色有幾種,沒法保證白色點數相同。那白色點數相同的保證就用外部限制來實現,所以我們先枚舉所需顏色種類數k = 0~D+1。轉移:dp[i][S] = sum(C(D - 1, k - bc[S1]) * dp[i-1][S0]),這里S和S0的高位要相同,而S1 = (S % 2) << 1 | (S0 % 2)表示上一條邊的頂點和當前邊的頂點顏色組成的狀態位,bc[]表示白色點的個數。邊界:dp[1][S] = C(D - 1, k - bc[S])。
轉移只與i-1有關,可矩陣快速冪優化。
總結:想法要大膽,看到n較大也不一定就放棄思考dp做法了,因為還可以用矩陣快速冪來保證復雜度。
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> pii; #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define re_(i,a,b) for(int i = (a);i < (b);++i) #define dwn(i,a,b) for(int i = (a);i >= (b);--i)const int N = 1e4 + 5; const int mod = 998244353;LL pw, fac[N], ifac[N]; int D, n = 4;void dbg() {puts (""); } template<typename T, typename... R>void dbg (const T &f, const R &... r) {cout << f << " ";dbg (r...); } template<typename Type>inline void read (Type &xx) {Type f = 1;char ch;xx = 0;for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';xx *= f; } void read() {} template<typename T, typename ...R>void read (T &x, R &...r) {read (x);read (r...); }struct Mat {LL m[5][5];Mat() {memset (m, 0, sizeof m);} }; Mat mul (Mat a, Mat b) {Mat ret;re_ (i, 0, n) {re_ (j, 0, n) {LL s = 0;re_ (k, 0, n) s = (s + a.m[i][k] * b.m[k][j] % mod) % mod;ret.m[i][j] = s;}}return ret; } Mat q_pow (Mat a, LL b) {Mat ret;re_ (i, 0, n) ret.m[i][i] = 1;for (; b; b >>= 1) {if (b & 1) ret = mul (ret, a);a = mul (a, a);}return ret; }LL q_pow (LL a, LL b) {LL ret = 1;for (; b; b >>= 1) {if (b & 1) ret = ret * a % mod;a = a * a % mod;}return ret; }void init_C (int n) {fac[0] = 1;rep (i, 1, n) fac[i] = fac[i - 1] * i % mod;ifac[n] = q_pow (fac[n], mod - 2);dwn (i, n - 1, 0) ifac[i] = (i + 1) * ifac[i + 1] % mod; }LL C (int x, int y) {if (y < 0 || x < y) return 0;return fac[x] * ifac[y] % mod * ifac[x - y] % mod; }int main() {init_C (N - 5);read (pw, D);LL ans = 0;rep (k, 0, D + 1) {Mat m0, bas;re_ (S, 0, n) {re_ (S0, 0, n) {if (S / 2 != S0 / 2) continue;int u = (S % 2) << 1 | (S0 % 2);m0.m[S][S0] = C (D - 1, k - __builtin_popcount (u) );}}re_ (S, 0, n) bas.m[S][0] = C (D - 1, k - __builtin_popcount (S) );Mat m1 = mul (q_pow (m0, pw - 1), bas);ans = (ans + m1.m[0][0] + m1.m[3][0]) % mod;}printf ("%lld\n", ans);return 0; }H-線段樹
思路
即ex題。官方題解的兩種做法都太難了,但是操作1的區間向下整除會導致數字快速減小,而操作2的區間覆蓋會導致整個數組的豐富程度下降,所以我們會想嘗試加一個tag,表示區間的數字是否完全相同,在區間數字完全相同時停止往下遍歷。
加這個tag的思路的主要來源是這道線段樹模板題:花神游歷各國,hdu上有一道題,區間求歐拉函數+求區間和,也是一樣的思路。
加這個tag的思路很容易理解,也能AC,但我不清楚它在這題的復雜度對不對。
實現
總結
以上是生活随笔為你收集整理的AtCoder abc256全题解(区间合并模板、矩阵快速幂优化dp、线段树……)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 知乎高赞!怎么自学 python,大概要
- 下一篇: 哔哩哔哩(B 站)刚刚崩了