Preface
“标题说是专题其实只是题单”系列(1/1)
树型DP
2070D - Tree Jumps
https://codeforces.com/problemset/problem/2070/D
Idea: 从深度上至下记录$dp[u]$,$u$为路径结尾点,$\sum dp[i]$即可能数量
双指针
2028C - Alice’s Adventures in Cutting Cake
https://codeforces.com/problemset/problem/2028/C
Idea: $dp_1[u]$记录选前$u$个可以满足的最大怪物数量,双指针维护;同时,逆序后可易求后缀$dp_2[v]$记录后$v$个可以满足的最大怪物数量,维护手段一致
#include "bits/stdc++.h"
using namespace std;
typedef long long ll; typedef double lf; typedef pair<ll, ll> II; typedef vector<ll> vec;
const inline void fast_io() { ios_base::sync_with_stdio(false); cin.tie(0u); cout.tie(0u); }
const lf PI = acos(-1);
const lf EPS = 1e-8;
const ll INF = 1e18;
const ll MOD = 998244353;
const ll DIM = 1e5;
int main() {
fast_io();
/* El Psy Kongroo */
ll t; cin >> t;
while (t--) {
ll n,m,v; cin >> n >> m >> v;
vec a(n + 1); for (ll i = 1; i <= n;i++) cin >> a[i];
vec pfx = a; for (ll i = 1; i <= n;i++) pfx[i] += pfx[i - 1];
pfx.push_back(pfx.back());
vec dp1(n + 2), dp2(n + 2);
// dp1[i] i之前(不包括)可达最大数目
for (ll i = 1, j = 1, sm = 0; i <= n; i++) {
while (j <= n && sm < v) {
sm += a[j], j++;
dp1[j] = max(dp1[j], dp1[j - 1]);
}
if (sm >= v) dp1[j] = dp1[i] + 1;
sm -= a[i];
}
for (ll i = 1; i <= n + 1;i++) dp1[i] = max(dp1[i], dp1[i - 1]);
if (dp1[n + 1] < m) {
cout << -1 << endl;
continue;
}
reverse(a.begin() + 1, a.end());
// dp2[i] i之后(不包括)可达最大数目
for (ll i = 1, j = 1, sm = 0; i <= n; i++) {
while (j <= n && sm < v) {
sm += a[j], j++;
dp2[j] = max(dp2[j], dp2[j - 1]);
}
if (sm >= v) dp2[j] = dp2[i] + 1;
sm -= a[i];
}
for (ll i = 1; i <= n + 1;i++) dp2[i] = max(dp2[i], dp2[i - 1]);
reverse(dp2.begin(), dp2.end());
// 找一段[i,j]段和最大且dp1[i] + dp2[j]>=m
ll res = 0;
for (ll i = 1, j = 0, sm = 0; i < n + 1; i++) {
while (j <= n && dp1[i] + dp2[j + 1] >= m) j++;
if (dp1[i] + dp2[j] >= m)
res = max(res, pfx[j] - pfx[i - 1]);
}
cout << res << endl;
}
return 0;
}
背包变种
子集和 / 方案总数
https://codeforces.com/contest/2086/problem/D;https://zhuanlan.zhihu.com/p/1891280211527590056;https://oi.wiki/dp/knapsack/#%E6%B1%82%E6%96%B9%E6%A1%88%E6%95%B0
$c_i$为每种数量
观察到每种字符只能出现在奇数位置或偶数位置之一后,考虑分配$c_i$中几部分到奇数位置
很显然记总共有$n$个位置可取奇数$odd = \lceil n/2 \rceil$个位置,选择$c_i$中某几个构成集合$S$,使得$\sum_{j \in S} c_j <= odd$
计这样的子集个数,实际上为01背包问题;记$i$为背包大小(总位置数量)
$ dp_i = \sum_{j=odd}^{c_j} dp_{i - c_j}$
从后往前递推即可
AC Code
int main() { fast_io(); /* El Psy Kongroo */ comb::prep(); ll t; cin >> t; while (t--) { ll sum = 0; vec c(26 + 1); for (ll i = 1; i <= 26; i++) cin >> c[i], sum += c[i], sum %= MOD; ll odd = ceil((lf)sum/2); ll even = sum - odd; vec dp(odd + 1); dp[0] = 1; for (ll i = 1; i <= 26; i++) if (c[i]) for (ll j = odd; j >= c[i];j--) dp[j] += dp[j - c[i]], dp[j] %= MOD; ll ways = dp[odd]; // 1~26中取出数字填充奇数位数方法个数 // 设选取于奇数方案一定 // 设奇数位上的数字*类型*为 j_i (1~26) // 奇数位方法为 odd! / c[j_1]! * c[j_2]! * c[j_3]! * ... // 偶数位方法为 even! / c[j_4]! * c[j_5]! * c[j_6]! * ... // 总方法 odd! * even! / c[j_1]! * c[j_2]! ... * c[j_n]! // ncomb = odd! * even! / c_1! * c_2! ... * c_26! // 带入选数方案即 // ans = ways * ncomb ll ncomb = comb::fac[odd] % MOD * comb::fac[even] % MOD; ll icomb = 1; for (ll i = 1;i <= 26;i++) icomb *= comb::ifac[c[i]], icomb %= MOD; ll ans = ways % MOD * ncomb % MOD * icomb % MOD; cout << ans << endl; } return 0; }
南昌 2025 邀请赛 - G Exploration
递推式很简单;记$dp_{i,j}$为从点$i$走$j$步可达最大边权积;实现上由于原图有(自)环DFS/BFS处理这个dp不好写
vector<vec> dp(n + 1, vec(33, 1));
for (ll i = 1;i <= 32;i++)
for (ll u = 1; u <= n;u++)
for (auto [v,d] : G[u])
dp[u][i] = max(dp[u][i], dp[v][i-1] * d);