DP 好题记录
Armor and Weapons
设
注意答案不会太大,因为当没有加成时,在
于是我们考虑换量:设
初始化
qwq
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 2e5 + 10; int n, m, t; map<pair<int, int>, int> EX; int f[N], g[N]; int val(int i){ int res = i + g[i]; if(EX.count({i, g[i]})) res++; return res; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); bool swp = false; cin >> n >> m >> t; if(n > m) swap(n, m), swp = true; for(int i = 1; i <= t; i++){ int x, y; cin >> x >> y; if(swp) swap(x, y); EX[make_pair(x, y)] = 1; } int ans = 0; g[1] = 1; if(n == 1 && m == 1){cout << 0; return 0;} while(++ans){ for(int i = 1; i <= n; i++) if(g[i]) f[i] = max(f[i], min(m, val(i))), f[min(n, val(i))] = max(f[min(n, val(i))], g[i]); for(int i = n; i; i--) g[i] = max(g[i + 1], f[i]), f[i] = 0; // cout << g[n] << "\n"; if(g[n] >= m){cout << ans; return 0;} } return 0; }
Formalism for Formalism
首先考虑转化问题,我们称一个等价集合是一个集合元素全部等价的集合,问题转化成求集合数量。
但这样还不好做,于是我们考虑用一个集合中字典序最小的那个字符串来代替该集合,这是常见的
考虑从前往后填数,现在考虑第
注意到可能填的数只有
找到
于是我们预处理出每种状态的后继即可。
qwq
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e5 + 10, V = 10, mod = 998244353; int n, m, nxt[(1 << V + 2)][V + 2], g[(1 << V + 2)], f[(1 << V + 2)]; bool con[V + 2][V + 2]; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; for(int i = 1; i <= m; i++){ int x, y; cin >> x >> y; con[x][y] = con[y][x] = true; } g[0] = 1; for(int S = 0; S < (1 << V); S++){ for(int i = 0; i < V; i++){ if(S & (1 << i)){nxt[S][i] = -1; continue;} for(int j = 0; j < V; j++) if(con[i][j] && (j < i || (S & (1 << j)))) nxt[S][i] |= (1 << j); } } for(int i = 1; i <= n; i++){ for(int S = 0; S < (1 << V); S++){ if(!g[S]) continue; //这个优化很重要! for(int ths = 0; ths < V; ths++){ if(nxt[S][ths] != -1) f[nxt[S][ths]] = (f[nxt[S][ths]] + g[S]) % mod; } } for(int i = 0; i < (1 << V); i++) g[i] = f[i], f[i] = 0; } int ans = 0; for(int i = 0; i < (1 << V); i++) ans = (ans + g[i]) % mod; cout << ans; // system("pause"); return 0; }
P3643 [APIO2016] 划艇
首先容易设计出朴素 DP:设
但是
如何优化呢?注意到题目中数值上限有
令第
令
但是区间中可能会有一些
单独考虑是困难的,于是我们考虑对它们整体考虑。首先分析没有
从而我们可以得出状态转移方程:
初始化
但这样是
qwq
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 500 + 10, mod = 1e9 + 7; int n, m, f[N][2 * N], l[N], r[N], rk[2 * N], s[N][2 * N], C[N]; int qpow(int x, int y){ int ret = 1; while(y){ if(y & 1) ret = ret * x % mod; x = x * x % mod; y >>= 1; } return ret; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; for(int i = 1; i <= n; i++) cin >> l[i] >> r[i], r[i]++, rk[i] = l[i], rk[i + n] = r[i]; sort(rk + 1, rk + 2 * n + 1); m = unique(rk + 1, rk + 2 * n + 1) - (rk + 1); for(int i = 1; i <= n; i++) l[i] = lower_bound(rk + 1, rk + m + 1, l[i]) - rk, r[i] = lower_bound(rk + 1, rk + m + 1, r[i]) - rk; for(int i = 0; i < m; i++) s[0][i] = 1; for(int j = 1; j < m; j++){ int len = rk[j + 1] - rk[j], tot = 0; C[0] = len; for(int i = 1; i <= n; i++) C[i] = (C[i - 1] * (i + len) % mod) * qpow(i + 1, mod - 2) % mod; for(int i = 1; i <= n; i++){ tot = 0; if(l[i] <= j && r[i] > j){ for(int lst = i - 1; lst >= 0; lst--){ f[i][j] = (f[i][j] + s[lst][j - 1] * C[tot] % mod) % mod; if(l[lst] <= j && r[lst] > j) tot++; } } s[i][j] = (s[i][j - 1] + f[i][j]) % mod; } } int ans = 0; for(int i = 1; i <= n; i++) ans = (ans + s[i][m - 1]) % mod; cout << ans; // system("pause"); return 0; } /* f[i][j] : 前 i 个数,a[i] 在 [j, j + 1) 枚举使 [lst + 1, j] 都在 [rk[j], rk[j + 1]) 的 lst(0 <= lst < i) f[i][j] = \sum{s[lst][j - 1] * C(rk[j] - rk[j - 1] + cnt - 1, cnt)} 初始化:f[0][0] = 1 C(rk[j + 1] - rk[j], 1) = rk[j + 1] - rk[j] C(x + 1, y + 1) = jc[x + 1] * jcinv[y + 1] * jcinv[x - y] C(x, y) = jc[x] * jcinv[y] * jcinv[x - y] C(x + 1, y + 1) = C(x, y) * (x + 1) * inv[y + 1] */2024-07-06 21:45:00 星期六
Interesting Problem Easy/Hard
首先给出一个关键但很显然的性质:一个决策只被前面的决策影响。于是我们可以设计一个区间
-
消掉
后再消 。 -
找到一个断点
,先消 再消 (注意这里的先后顺序是不固定的,即假如 的可消除对数大于 所需要的消除对数 ,可以通过先操作 次后在再进行 的消除操作)。
时间复杂度
优化是比较困难的,考虑进一步发掘题目中的性质。观察到答案一定是由一段段全部消除的区间拼接而成的。受到上面的启发,我们可以再次设计出一个
qwq
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 800 + 10; int n, f[N][N], g[N], a[N]; int Get(int id, int val){ int opt = (id - val) / 2; if(opt > id / 2 || ((id - val) & 1) || val > id) return -1; return opt; } void solve(){ cin >> n; memset(f, 0x3f, sizeof f); memset(g, 0, sizeof g); int INF = f[0][0]; for(int i = 1; i <= n; i++) cin >> a[i], f[i][i - 1] = 0; for(int len = 2; len <= n; len += 2){ for(int l = 1; l + len - 1 <= n; l++){ int r = l + len - 1; if(Get(l, a[l]) != -1 && Get(l, a[l]) >= f[l + 1][r - 1]) f[l][r] = Get(l, a[l]); for(int k = l; k < r; k++){ f[l][r] = min(f[l][r], max(f[l][k], f[k + 1][r] - (k - l + 1) / 2)); } // if(f[l][r] > (l - 1) / 2 || (r - l + 1) % 2) f[l][r] = INF; // cout << l << " " << r << " " << f[l][r] << "\n"; } } for(int i = 2; i <= n; i++){ g[i] = g[i - 1]; for(int j = 1; j <= i; j++) if(g[j - 1] >= f[j][i]) g[i] = max(g[i], g[j - 1] + (i - j + 1) / 2); // cout << g[i] << "\n"; } cout << g[n] << "\n"; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int T; cin >> T; while(T--) solve(); return 0; }
ABC176F Brave CHAIN
容易有简单
-
不换卡:
-
换一张:
-
换两张:
当然有一些类似的不予考虑。我们发现其实没有多少状态被影响到了,若被影响到的多,原因也是
对于第一种,显然对于任意
对于第二种,被影响到的状态只有
对于第三种,若满足
qwq
#include<bits/stdc++.h> using namespace std; const int N = 2000 + 10; int n, f[N][N], arr[N * 3]; void MAX(int& x, int y){if(x < y) x = y;} struct opt{ int k0, k1, val; }; void upd(int a, int b, int w){ MAX(f[a][b], w); MAX(f[a][0], w); MAX(f[0][0], w); } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; memset(f, -0x3f, sizeof f); int allsum = 0; for(int i = 1; i <= 3 * n; i++) cin >> arr[i]; upd(arr[1], arr[2], 0); upd(arr[2], arr[1], 0); for(int i = 1; i < n; i++){ queue<opt> Q; int c = arr[3 * i], d = arr[3 * i + 1], e = arr[3 * i + 2], ad = 0; // f[i][a][b] if(c == d && d == e) ad++, allsum++; // f[i][a][c], f[i][a][d], f[i][a][e] for(int a = 1; a <= n; a++){ if(d == e) Q.push((opt){a, c, f[a][d] + 1}); Q.push((opt){a, c, f[a][0]}); if(c == e) Q.push((opt){a, d, f[a][c] + 1}); Q.push((opt){a, d, f[a][0]}); if(d == c) Q.push((opt){a, e, f[a][d] + 1}); Q.push((opt){a, e, f[a][0]}); } // f[i][c][d], f[i][c][e], f[i][d][e] Q.push((opt){c, d, f[e][e] + 1}); Q.push((opt){c, d, f[0][0]}); Q.push((opt){c, e, f[d][d] + 1}); Q.push((opt){c, e, f[0][0]}); Q.push((opt){d, e, f[c][c] + 1}); Q.push((opt){d, e, f[0][0]}); // update while(!Q.empty()){ opt ths = Q.front(); Q.pop(); int a = ths.k0, b = ths.k1, w = ths.val - ad; upd(a, b, w); upd(b, a, w); } } int ans = 0; for(int a = 1; a <= n; a++){ for(int b = 1; b <= n; b++){ MAX(ans, f[a][b] + (a == b && b == arr[n * 3])); } } cout << ans + allsum; return 0; } /* f[i][a][b] = f[i - 1][a][b] + [c = d = e] f[i][a][c] = f[i - 1][a][b] + [b = d = e] f[i][c][d] = f[i - 1][a][b] + [a = b = e] */
AGC007E Shik and Travel
非常厉害的一道题!
首先容易发现答案具有单调性,于是先通过二分答案
接下来注意到一条边只能走两次,其实就是限制了当进入一棵子树,必须先把里面的所有点经过之后才能离开这棵子树。也就是说,当处理到一颗子树时,需要把它的一颗子树处理完,然后从一个叶子到另一颗子树的叶子,再处理完这个子树,然后离开。
不难发现我们只需要知道两颗子树从
但是想要再优化就没有那么简单了。首先需要注意到一个显然的但是容易被忽略的性质:若
但是这样的时间复杂度?我们设节点
code:
qwq
#include<bits/stdc++.h> #define ll long long #define pir pair<ll, ll> using namespace std; const int N = 2e5 + 10; int n, son[N][3]; ll dis[N][2]; vector<pir> f[N], g[N], vec[N]; ll lim; void clrvec(vector<pir> &v){vector<pir> __qwq; swap(__qwq, v);} void Merge_array(int u){ int j = 0; vector<pir> tmp; for(int i = 0; i < f[u].size(); i++){ while(j < g[u].size() && (g[u][j].first < f[u][i].first || (g[u][j].first == f[u][i].first && g[u][j].second < f[u][i].second))) tmp.push_back(g[u][j++]); tmp.push_back(f[u][i]); } while(j < g[u].size()) tmp.push_back(g[u][j++]); if(tmp.empty()) return; int ttt = 0; vec[u].push_back(tmp[0]); for(int i = 1; i < tmp.size(); i++){ if(tmp[i].second < vec[u][ttt].second) ttt++, vec[u].push_back(tmp[i]); } } void dfs(int u){ if(!son[u][2]){ vec[u].push_back(make_pair(0ll, 0ll)); return;} for(int i = 0; i < 2; i++) dfs(son[u][i]); int ls = son[u][0], rs = son[u][1], j = 0, sum = dis[u][0] + dis[u][1]; for(int i = 0; i < vec[ls].size(); i++){ while(j < vec[rs].size() && vec[ls][i].second + vec[rs][j].first + sum <= lim) j++; if(j) f[u].push_back(make_pair(vec[ls][i].first + dis[u][0], vec[rs][j - 1].second + dis[u][1])); } swap(ls, rs); j = 0; for(int i = 0; i < vec[ls].size(); i++){ while(j < vec[rs].size() && vec[ls][i].second + vec[rs][j].first + sum <= lim) j++; if(j) g[u].push_back(make_pair(vec[ls][i].first + dis[u][1], vec[rs][j - 1].second + dis[u][0])); } Merge_array(u); clrvec(f[u]); clrvec(g[u]); //cout << u << " " << vec[u].size() << " qwq" << "\n"; //for(int i = 0; i < vec[u].size(); i++) cout << vec[u][i].first << " " << vec[u][i].second << "\n"; } bool check(ll V){ //cout << "\n" << V << "\n" << ":::" << "\n"; for(int i = 1; i <= n; i++) clrvec(f[i]), clrvec(g[i]), clrvec(vec[i]); lim = V; dfs(1); return vec[1].size(); } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; for(int i = 2; i <= n; i++){ int x, y; cin >> x >> y; son[x][son[x][2]] = i; dis[x][son[x][2]] = y; ++son[x][2]; } ll l = 0, r = 3e10, ans = 3e10; while(l <= r){ ll mid = (l + r >> 1); if(check(mid)) r = mid - 1, ans = mid; else l = mid + 1; } cout << ans; return 0; }
P6383 『MdOI R2』Resurrection
首先观察答案形态,不难发现等价于每次找到当前树上两点所在连通块的根进行连边。现在考虑断边
所以不难设计出
qwq
#include<bits/stdc++.h> #define ll long long #define pb emplace_back using namespace std; const int N = 3e3 + 10, mod = 998244353; int n; ll f[N][N], s[N]; vector<int> G[N]; int Mod(int x){return (x % mod + mod) % mod;} void dfs(int u, int fa){ for(int j = 1; j <= n; j++) f[u][j] = 1; if(u != n) f[u][1] = 0; for(auto v : G[u]){ if(v == fa) continue; dfs(v, u); for(int j = 1; j <= n; j++) s[j] = (s[j - 1] + f[v][j]) % mod; for(int j = 1; j <= n; j++) f[u][j] = f[u][j] * Mod(s[j + 1] - s[1]) % mod; } // for(int i = 1; i <= n; i++) cout << u << " " << i << " " << f[u][i] << "\n"; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; for(int i = 1; i < n; i++){ int x, y; cin >> x >> y; G[x].pb(y); G[y].pb(x); } dfs(n, 0); cout << f[n][1] << "\n"; return 0; }
P6846 [CEOI2019] Amusement Park
首先不难注意到假如一个有向图用
考虑
时间复杂度
qwq
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 20 + 1, M = N * N + 10, maxS = (1ll << N) + 10; const ll mod = 998244353; void ADD(ll &x, ll y){ x += y; (x >= mod) ? (x -= mod) : x; } int n, m, con[N], pd[maxS], pc[maxS]; ll f[maxS]; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; for(int i = 1; i <= m; i++){ int x, y; cin >> x >> y; x--; y--; con[x] |= (1ll << y); con[y] |= (1ll << x); } f[0] = 1; for(int S = 1; S < (1ll << n); S++){ int st = 0; pc[S] = pc[S >> 1] + (S & 1); for(int i = 0; i < n; i++) if(S & (1ll << i)) st |= con[i]; pd[S] = (!(S & st)); //cout << S << " " << st << " " << pd[S] << "\n"; for(int S0 = S; S0; S0 = S & (S0 - 1)){ if(!pd[S0]) continue; //cout << S << " " << S0 << " " << f[S ^ S0] << "\n"; ADD(f[S], f[S ^ S0] * ((pc[S0] & 1ll) ? 1ll : (mod - 1ll)) % mod); } //cout << S << " " << f[S] << "\n"; } cout << f[(1ll << n) - 1] * m % mod * ((mod + 1) / 2) % mod; return 0; }
P10197 [USACO24FEB] Minimum Sum of Maximums P
首先记一个转化:
在两边插入两个固定的极大值,然后前面的
考虑每段内如何排列最好,显然是升序排序最好(降序的话就把
因此我们可以设计一个区间
-
合并相离的段:
-
确定一个段
的最大最小值:-
确定的段长度只有
(这个情况只有在只剩一个数的时候使用,即 ,最后合并即可,否则转移正确性无法保证): -
一般情况:
-
时间复杂度
qwq
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 300 + 5, MAXV = (1ll << 7) + 5, INF = 1e6 + 5; int n, m, a[N], id[N], L[N], R[N], len[N], siz[MAXV], cnt, vis[N]; ll ans = 2 * INF, f[N][N][MAXV]; void chkmin(ll& x, ll y){if(y < x) x = y;} signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); memset(f, 0x3f, sizeof f); cin >> n >> m; n += 2; a[1] = a[n] = INF; m += 2; id[1] = 1; id[m] = n; for(int i = 2; i < n; i++) cin >> a[i], ans += 2 * a[i]; for(int i = 2; i < m; i++) cin >> id[i], id[i]++, vis[id[i]] = 1; for(int i = 1; i < m; i++){ if(id[i + 1] == id[i] + 1) ans += abs(a[id[i]] - a[id[i] + 1]); else{ len[cnt] = id[i + 1] - id[i] - 1; L[cnt] = a[id[i]], R[cnt] = a[id[i + 1]]; if(L[cnt] > R[cnt]) swap(L[cnt], R[cnt]); cnt++; } } int tt = 0; for(int i = 2; i < n; i++) if(!vis[i]) a[++tt] = a[i]; sort(a + 1, a + tt + 1); n = tt; for(int i = 1; i <= n; i++) f[i + 1][i][0] = 0; for(int s = 0; s < (1ll << cnt); s++) for(int i = 0; i < cnt; i++){ if(s & (1ll << i)) siz[s] += len[i]; } for(int lll = 1; lll <= n; lll++){ for(int l = 1; l + lll - 1 <= n; l++){ int r = l + lll - 1; for(int s = 0; s < (1ll << cnt); s++){ //if(s == 0) f[l][r][s] = 0; f[l][r][s] = min(f[l + 1][r][s], f[l][r - 1][s]); if(siz[s] > r - l + 1) continue; for(int s0 = s; s0; s0 = s & (s0 - 1)) chkmin(f[l][r][s], f[l][l + siz[s0] - 1][s0] + f[l + siz[s0]][r][s ^ s0]); for(int i = 0; i < cnt; i++){ if((1ll << i) & s){ ll val = abs(R[i] - a[r]) + a[r] - a[l] + abs(a[l] - L[i]); if(siz[s] == 1 && len[i] == 1) chkmin(f[l][r][s], val); else if(len[i] > 1) chkmin(f[l][r][s], f[l + 1][r - 1][s ^ (1ll << i)] + val); } } //cout << l << " " << r << " " << s << " " << f[l][r][s] << "\n"; } } } cout << (ans + f[1][n][(1ll << cnt) - 1]) / 2 - 2 * INF; return 0; }
AGC061E Increment or XOR
很好的一道划分
首先观察结构,发现
于是我们考虑把前
-
已经满足条件,不需要操作:此时需要满足
,直接 。 -
由若干个阶段拼起来:首先从
进行操作到 ,接着通过若干次进位,最后一个阶段从 到 。注意因为我们的进位至多进行一次,因此每次中间的进位操作一定要满足 第 位是 。具体转移是
这个东西就是一个最短路的形式,
qwq
#include<bits/stdc++.h> #define ll long long #define int long long using namespace std; const int MAXV = (1ll << 8) + 10, N = 10; const ll INF = 1e18; void chkmin(ll& x, ll y){if(y < x) x = y;} bool ex(int s, int i){return (s & (1ll << i));} int n, a[N], S, T, st[MAXV], qwq[4]; bool vis[MAXV]; ll c[N], f[2][2][MAXV], g[2][2][MAXV], A, w[MAXV], dis[MAXV]; void upd(int bt, int x){ //cerr << bt << "\n"; for(int s = 0; s < (1 << n); s++){ vis[s] = 0; if(ex(st[s], bt) == qwq[x]) dis[s] = g[x][1][s]; else dis[s] = INF; } for(int rnd = 1; rnd <= (1 << n); rnd++){ int p = -1; for(int s = 0; s < (1 << n); s++) if((p == -1 || dis[p] > dis[s]) && (!vis[s])) p = s; if(p == -1 || dis[p] >= INF) return; vis[p] = 1; //cerr << p << " " << dis[p] << "\n"; for(int s = 0; s < (1 << n); s++){ if(vis[s] || (!ex(st[p ^ s], bt))) continue; chkmin(dis[s], dis[p] + g[1][1][s ^ p]); } for(int s = 0; s < (1ll << n); s++){ for(int y = 0; y < 2; y++) if(qwq[2 + y] == (ex(st[s], bt) ^ 1)) chkmin(f[x][y][s ^ p], dis[p] + g[1][y][s]); } } } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> S >> T >> A; qwq[1] = 0; qwq[3] = 1; for(int i = 0; i < n; i++) cin >> a[i] >> c[i]; for(int s = 0; s < (1 << n); s++) for(int i = 0; i < n; i++) if(ex(s, i)) w[s] += c[i], st[s] ^= a[i]; for(int x = 0; x < 2; x++) for(int y = 0; y < 2; y++) for(int s = 0; s < (1 << n); s++) f[x][y][s] = w[s] + y * A; for(int i = 0; i < 40; i++){ qwq[0] = ex(S, i); qwq[2] = ex(T, i); for(int x = 0; x < 2; x++) for(int y = 0; y < 2; y++) for(int s = 0; s < (1 << n); s++){ //cerr << x << " " << y << " " << s << " " << f[x][y][s] << "\n"; g[x][y][s] = f[x][y][s]; if((qwq[x] ^ ex(st[s], i)) != qwq[2 + y]) f[x][y][s] = INF; } for(int x = 0; x < 2; x++) upd(i, x); } ll ans = INF; for(int s = 0; s < (1 << n); s++) chkmin(ans, f[0][0][s]); if(ans >= INF){cout << -1; return 0;} cout << ans; return 0; }
AGC052C Nondivisible Prefix Sums
首先由于可以直接重排,顺序是不重要的,考虑一个一个将这些数加入序列中。假设现在已经确定了一个前缀信息,考虑接下来放什么数,显然只会有一种数是不合法的,为了保持合法,那么一个很 naive 的想法就是保持手上有至少
接下来,为了方便分析,不妨让所有的数都乘上众数的逆元,这样众数就是
于是一个序列合法的充要条件就是:
直接算合法的其实不好做,于是考虑算不合法的。那么后面的东西不能超过
最后只剩总方案数了,但是注意上面的东西都没有考虑到一个东西,就是总共和等于
qwq
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 5000 + 10; const ll mod = 998244353; int n; ll P, jc[N], jcinv[N], f[N], g[N], h[N], sum[N]; void ADD(ll& x, ll y){x += y; (x >= mod) ? x -= mod : 0;} ll qpow(ll x, int y){ ll ret = 1; for(; y; y >>= 1, x = x * x % mod) if(y & 1) ret = ret * x % mod; return ret; } ll C(int x, int y){return jc[x] * jcinv[x - y] % mod * jcinv[y] % mod;} signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> P; jc[0] = jcinv[0] = 1; for(int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mod, jcinv[i] = qpow(jc[i], mod - 2); g[0] = 1; for(int i = 1; i <= n; i++) g[i] = (P - 1) * h[i - 1] % mod, h[i] = (g[i - 1] + h[i - 1] * (P - 2) % mod) % mod; ll ans = (P - 1) * h[n] % mod; sum[0] = 1; if(n % P && P <= n) ADD(ans, mod - (P - 1)); for(int i = 1; i <= n; i++){ for(int j = 1; j <= n; j++) sum[j] = (f[j] + sum[j - 1]) % mod; for(int j = 1; j <= n; j++){ f[j] = (sum[j - 1] + mod - ((j - (P - 1) >= 0) ? sum[j - P + 1] : 0ll)) % mod; if((n - i - j) % P != 0 && n - i > j + P - 1) ADD(ans, mod - f[j] * C(n, i) % mod * (P - 1) % mod); } sum[0] = 0; } cout << ans; return 0; }
AGC040E Prefix Suffix Addition
非常好
尝试使用
不难发现
然后直接维护连续段即可,因为我不想分讨所以最后写的是二分做法。
qwq
#include<bits/stdc++.h> #define ll long long #define pb emplace_back using namespace std; const int N = 2e5 + 10; int n, a[N]; struct node{ int l, r, val; }; vector<node> f[N]; void chkmin(int& x, int y){if(y < x) x = y;} int get(int i, int pos){ int ans = 1e9; for(auto qwq : f[i - 1]) chkmin(ans, qwq.val + (pos < qwq.l) + (a[i - 1] - qwq.l < a[i] - pos)); return ans; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; f[0].pb((node){0, 0, 0}); for(int i = 1; i <= n + 1; i++){ for(int l = 0; l <= a[i];){ int r = l, val = get(i, l); int L = l, R = a[i], mid; while(L <= R){ mid = (L + R >> 1); if(get(i, mid) == val) L = mid + 1, r = mid; else R = mid - 1; } f[i].pb((node){l, r, val}); l = r + 1; } } cout << f[n + 1][0].val; return 0; }
本文作者:Little_corn
本文链接:https://www.cnblogs.com/little-corn/p/18281742
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步