1. 在两个数列之间
有两个整数数列 和 。我们的任务是找出满足以下条件的数列 :
- 对 ,
- 对 ,
- 所有 都是整数
满足这些条件的整数数列有多少个?输出答案模 的余数。
限制:
分析
记 dp[i][j]
表示长度为 且结尾的值为 的合法 的个数
状态转移需要用前缀和优化
代码实现
#include <bits/stdc++.h> #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; using ll = long long; const int mod = 998244353; //const int mod = 1000000007; struct mint { ll x; mint(ll x=0):x((x%mod+mod)%mod) {} mint operator-() const { return mint(-x); } mint& operator+=(const mint a) { if ((x += a.x) >= mod) x -= mod; return *this; } mint& operator-=(const mint a) { if ((x += mod-a.x) >= mod) x -= mod; return *this; } mint& operator*=(const mint a) { (x *= a.x) %= mod; return *this; } mint operator+(const mint a) const { return mint(*this) += a; } mint operator-(const mint a) const { return mint(*this) -= a; } mint operator*(const mint a) const { return mint(*this) *= a; } mint pow(ll t) const { if (!t) return 1; mint a = pow(t>>1); a *= a; if (t&1) a *= *this; return a; } // for prime mod mint inv() const { return pow(mod-2); } mint& operator/=(const mint a) { return *this *= a.inv(); } mint operator/(const mint a) const { return mint(*this) /= a; } }; istream& operator>>(istream& is, mint& a) { return is >> a.x; } ostream& operator<<(ostream& os, const mint& a) { return os << a.x; } int main() { int n; cin >> n; vector<int> a(n), b(n); rep(i, n) cin >> a[i]; rep(i, n) cin >> b[i]; const int M = 3001; vector<mint> dp(M+1); dp[0] = 1; rep(i, n) { vector<mint> p(M+1); swap(dp, p); rep(j, M) { if (a[i] <= j and j <= b[i]) { dp[j] += p[j]; } if (j < M) p[j+1] += p[j]; } } mint ans; rep(i, M) ans += dp[i]; cout << ans << '\n'; return 0; }
2. 子序列的贡献
给定一个长度为 的数列 ,求 的一个长度为 子序列 ,那么该子序列的贡献就是
希望你求出这样一个子序列,使得这个子序列贡献和最大,你只需要输出这个最大值。
限制:
分析
记 dp[i][j]
表示在序列 的前 个数中选 个数时 的最大值
代码实现
#include <bits/extc++.h> #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; using ll = long long; inline void chmax(ll& x, ll y) { if (x < y) x = y; } int main() { int n, m; cin >> n >> m; vector<ll> a(n); rep(i, n) cin >> a[i]; const ll INF = 1e18; vector dp(n+1, vector<ll>(m+1, -INF)); dp[0][0] = 0; rep(i, n) { rep(j, m+1) { chmax(dp[i+1][j], dp[i][j]); if (j) chmax(dp[i+1][j], dp[i][j-1]+a[i]*j); } } cout << dp[n][m] << '\n'; return 0; }
3. ABC232F
注意到操作2的本质其实是遍历序列 的所有可能的顺序有 种,记为 ,那么从 变到 的次数就是置换 的逆序对
然后考虑状压dp
记 dp[S]
表示序列 的前 个元素由集合 中的元素构成且使得 的最小费用
代码实现
#include <bits/stdc++.h> #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; using ll = long long; inline void chmin(ll& x, ll y) { if (x > y) x = y; } int main() { int n; ll x, y; cin >> n >> x >> y; vector<int> a(n), b(n); rep(i, n) cin >> a[i]; rep(i, n) cin >> b[i]; int n2 = 1<<n; const ll INF = 1e18; vector<ll> dp(n2, INF); dp[0] = 0; rep(s, n2) { int j = __builtin_popcount(s); rep(i, n) { if (s>>i&1) continue; ll cost = abs(a[i]-b[j])*x; cost += __builtin_popcount(s>>i)*y; chmin(dp[s|1<<i], dp[s]+cost); } } ll ans = dp[n2-1]; cout << ans << '\n'; return 0; }
4. ABC225F
对于相邻两张卡片,假设卡片上的字符串分别为 和 ,如果 ,显然交换两张卡片的顺序更好,那么我们可以按照这个规则来对所有卡片做一遍排序
然后考虑dp,记 dp[i][j]
表示在前 个字符串中选择 个字符串按次拼接起来得到的最小字符串。
但这样就有一个问题了,不同长度的字符串的大小关系不太好确定,所以需要把长度也加到dp里,这样一来状态的复杂度就是 ,其中 ,显然要炸
考虑从后往前dp,也就是先确定好后缀,这样就不会有问题了
代码实现
#include <bits/stdc++.h> #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; inline void chmin(string& a, string b) { if (b == "") return; if (a == "" or a > b) a = b; } int main() { int n, k; cin >> n >> k; vector<string> s(n); rep(i, n) cin >> s[i]; sort(s.rbegin(), s.rend(), [&](string a, string b) { return a+b < b+a; }); vector<string> dp(k+1); for (string a : s) { for (int j = k-1; j >= 0; --j) { if (dp[j] == "" and j) continue; chmin(dp[j+1], a+dp[j]); } } cout << dp[k] << '\n'; return 0; }
5. ABC249E
考察长度为 的单个字符被压缩后的长度:
记 dp[i][j]
表示前 个字符压缩后的长度为 的方案数,再令 为长度为 的单个字符被压缩后的长度
转移:
可以枚举最后一段完全相同的字符的子串,得到 dp[i][j] += dp[i-w][j-f(w)] * 25
状态数为 ,转移数为 ,时间复杂度为
可以用差分将转移数降到
代码实现
#include <bits/stdc++.h> #if __has_include(<atcoder/all>) #include <atcoder/all> using namespace atcoder; #endif #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; using mint = modint; const int ten[] = {0, 1, 10, 100, 1000, 10000}; int main() { int n, p; cin >> n >> p; mint::set_mod(p); vector dp(n+1, vector<mint>(n)); vector ds(n+1, vector<mint>(n)); auto f = [&](int x) { int res = 0; while (x) { ++res; x /= 10; } return res+1; }; for (int w = 1; w <= n; ++w) { dp[w][f(w)] = 26; } for (int i = 1; i <= n; ++i) { rep(j, n) { // for (int w = 1; w <= i; ++w) { // int nj = j-f(w); // if (nj < 0) continue; // dp[i][j] += dp[i-w][nj]*25; // } for (int k = 1; k <= 4; ++k) { int nj = j-k-1; if (nj < 0) continue; int lb = ten[k], ub = ten[k+1]; ub = min(ub, i); if (lb >= ub) continue; //for (int w = lb; w < ub; ++w) { // dp[i][j] += dp[i-w][nj]*25; //} // (i-ub+1) ... (i-lb) mint sum = ds[i-lb][nj] - ds[i-ub][nj]; dp[i][j] += sum*25; } ds[i][j] = ds[i-1][j] + dp[i][j]; } } mint ans; rep(j, n) ans += dp[n][j]; cout << ans.val() << '\n'; return 0; }
6. Taffy Permutation
给定仅由 和 构成的字符串 。统计满足以下条件的 的排列 ,并对答案模 :
- 对于任意一对满足 且 且 的 ,都成立
限制:
算法分析
记 dp[i][j]
表示已经填了前 个数,在剩下的数中恰有 个数满足它小于在前面 的位置上所填的数的最大值
用前缀和加速可以做到
代码实现
#include <bits/stdc++.h> #if __has_include(<atcoder/all>) #include <atcoder/all> using namespace atcoder; #endif #define rep(i, n) for (int i = 0; i < (n); ++i) using namespace std; using mint = modint998244353; int main() { int n; string s; cin >> n >> s; vector<mint> dp(n+1); dp[0] = 1; rep(i, n) { int m = n-i; vector<mint> old(m); swap(dp, old); if (s[i] == '1') { rep(j, m) dp[j] = old[j]*(n-i-j); } else { rep(j, m) dp[j] = old[j+1]*(j+1); mint sum; rep(j, m) { sum += old[j]; dp[j] += sum; } } } mint ans = dp[0]; cout << ans.val() << '\n'; return 0; }
7. 数组划分
给定一个长度为 的数组 ,你需要将数组划分为若干个互不相交的连续区间,使得每个区间中的元素总和不超过 ,并且数组的每个元素都恰好属于其中一个区间。
定义每个区间的权值为 ,其计算方式如下:
- 令 表示区间中未出现的最小非负整数
- 则
现在,你需要找到一种划分数组的方式,使得所有区间的权值之和最大,并输出这个最大权值。
注意,你无需输出具体的划分方式,只需要输出最大权值即可!
限制:
算法分析
做法1:
考虑动态规划,用 dp[i]
表示将数组 的位置划分为若干区间所得到的最大权值总和,答案即为
在 的结尾处必然存在以 为右端点划分出来的区间,于是可以枚举这个区间的左端点 ,并计算出区间 的 ,从而得到权值
于是有状态转移方程:
枚举 的时间复杂度为 ,计算区间 的时间复杂度为 ,整体时间复杂度为 。
做法2:
当一个区间的右端点 固定时,左端点 不断往左边移动的过程中,每移动一次,区间中就多一个整数, 的值就有机会在原来的基础上变大,一定不会变小,不难发现有单调性;
因此在枚举左端点 的过程中,可以顺便用类似双指针的方式,快速地把每次移动后区间对应的 求出来,从而省去 的复杂度,整体复杂度为 。
代码实现
#include <bits/stdc++.h> #define rep(i, n) for (int i = 1; i <= (n); ++i) using namespace std; using ll = long long; int a[200005]; ll dp[200005]; bool used[200005]; int main() { cin.tie(nullptr) -> sync_with_stdio(false); int n, m; cin >> n >> m; rep(i, n) cin >> a[i]; rep(i, n) { memset(used, 0, sizeof used); int mex = 0; ll sum = 0; for (int j = i; j >= 1; --j) { sum += a[j]; if (sum > m) continue; used[a[j]] = true; while (used[mex]) ++mex; ll w = (ll)mex*mex*mex*mex; dp[i] = max(dp[i], dp[j-1]+w); } } cout << dp[n] << '\n'; return 0; }
做法3:
当 从小到大遍历去求 的值时,用数组 last[x]
去记录整数 在位置 之前的最后一次出现的位置,这些位置就是 的值可能发生变化的位置。
又因为区间权值 ,根据上文中提到的转移方程,可以得出:,对于所有 都成立。
因此, 的值随着 的增大,是单调不降的;
对于右端点 相同且权值 相同的区间 ,选择 最大的区间去更新 的值即可,而 所记录的“整数 在位置 之前的最后一次出现位置”正好就具备了 值最大的条件。
至此,用 的时间复杂度从 到 枚举 作为 的值,从 的位置更新 即可;
而对于区间总和是否超过 的限制,可以预处理前缀和进行 地判断。
注意,若出现 且 的情况,则当枚举到 时,应当从 的位置更新 的值,因为如果 为 ,那么比它小的整数 必须包含在区间里面。
时间复杂度为 。
正解做法:
注意到:“ 是区间中未出现的最小非负整数”等价于“整数 均已经出现在区间中”。
再根据题目的限制条件:每个区间的元素和不能超过 ,可以得出 ,即 。
也就是说,对于一个右端点 固定的区间,其左端点 往左移动的过程中, 的值至多变化 次。
至此,可以像做法3一样,维护 数组,枚举 个位置来加速每一个 的更新。
事实上,在代码实现的时候,当出现区间元素和大于 的情况时,立刻跳出 的循环即可保证正确的时间复杂度。
时间复杂度为
代码实现
#include <bits/stdc++.h> #define rep(i, n) for (int i = 1; i <= (n); ++i) using namespace std; using ll = long long; int a[200005]; ll s[200005], dp[200005]; int last[200005]; int main() { cin.tie(nullptr) -> sync_with_stdio(false); int n, m; cin >> n >> m; rep(i, n) cin >> a[i]; rep(i, n) s[i] = s[i-1]+a[i]; rep(i, n) { last[a[i]] = i; dp[i] = dp[i-1]; int best = n; for (int x = 0; x <= m; ++x) { if (last[x] == 0) break; if (s[i]-s[last[x]-1] > m) break; best = min(best, last[x]); ll w = ll(x+1)*(x+1)*(x+1)*(x+1); dp[i] = max(dp[i], dp[best-1]+w); } } cout << dp[n] << '\n'; return 0; }
8. 区间划分
给定 个连成一串的符号。符号只可能是 +
或者是 -
。我们需要将这些符号划分成几个区间段落。每一段至少一个符号,至多 个符号( 为一个给定的整数)。
在一个段落中,若减号数量大于或等于加号,则称这个段落是负能量的;否则,就是正能量的。
请问如何划分,才能让负能量的段落达到最少,输出这个最少值。
限制:
算法分析
原题:重新分区
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2023-06-19 CodeStar2023年春第12周周赛普及进阶组