By 三石吖 2026.2
3.动态规划
8596 最长上升子序列
最大只有,显然可以考虑的做法
用表示以第个元素结尾的最大子段和
递推:考虑每一个小于的位置,如果,说明发现了一个上升段,转移方程为:
- 时间复杂度:
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
while(std::cin >> n){
if(!n){
break;
}
std::vector<int>a(n);
for(int i = 0; i < n; i++){
std::cin >> a[i];
}
std::vector<int>dp(n, 1);
for(int i = 1; i < n; i++){
for(int j = 0; j < i; j++){
if(a[i] > a[j]){
dp[i] = std::max(dp[i], dp[j] + 1);
}
}
}
int ans = *std::max_element(dp.begin(), dp.end());
std::cout << ans << '\n';
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
18708 最大子段和
考虑用表示以第个元素结尾的最大子段和
由于子段是连续的子数组,因此第个位置的状态只能由第个位置推导而来
如果字段长度为1,那么就是,否则是,于是有状态转移方程:
若求最小子段和也是同理
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>a(n);
for(int i = 0; i < n; i++){
std::cin >> a[i];
}
std::vector<int>dp(n);
dp[0] = a[0];
for(int i = 1; i < n; i++){
dp[i] = std::max(dp[i - 1] + a[i], a[i]);
}
int ans = *std::max_element(dp.begin(), dp.end());
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
19182 石子合并(基础版)
区间模板题
用表示合并这个区间石子的最小代价
一个区间的最小代价可以由更小的区间推出
形式化地,
然后枚举长度,枚举起点,递推即可
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>a(n + 1), pre(n + 1);
for(int i = 1; i <= n; i++){
std::cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
std::vector<std::vector<int>>dp(n + 1, std::vector<int>(n + 1, inf));
for(int i = 1; i <= n; i++){
dp[i][i] = 0;
}
for(int len = 2; len <= n; len++){
for(int i = 1; i + len - 1 <=n; i++){
int j = i + len - 1;
for(int k = i; k < j; k++){
dp[i][j] = std::min(dp[i][j], dp[i][k] + dp[k + 1][j] + pre[j] - pre[i - 1]);
}
}
}
std::cout << dp[1][n] << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
8597 石子划分问题
- 题目没说,实际上是可以排序的
用表示在第个石子处,划分为份,且第个石子属于第份的最小代价
设上一份石子划分的结束位置为,则
从前往后递推即可
-
注意:第个石子处最多划分出份
-
时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n, m;
std::cin >> n >> m;
std::vector<int>a(n + 1);
for(int i = 1; i <= n; i++){
std::cin >> a[i];
}
std::sort(a.begin(), a.end());
std::vector<std::vector<int>>dp(n + 1, std::vector<int>(m + 1, inf));
dp[0][0] = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= std::min(m, i); j++){
for(int k = 0; k < i; k++){
int d = a[i] - a[k + 1];
dp[i][j] = std::min(dp[i][j], dp[k][j - 1] + d * d);
}
}
}
std::cout << dp[n][m] << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
19185 01背包
入门题
用表示容量为的背包可以装的最大价值
每次考虑是否加入新物品,递推公式:
其中分别为第个物品的重量和价值
时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n,m;
std::cin >> m >> n;
std::vector<int>w(n), v(n);
for(int i = 0; i < n; i++){
std::cin >> w[i] >> v[i];
}
std::vector<int>dp(m + 1,0);
for(int i = 0; i < n; i++){
for(int j = m; j >= w[i]; j--){
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
std::cout << dp[m] <<"\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
9717 取数对弈
好题
表示从区间内甲先手能取到最高分
甲第一步要么取,要么取
①如果甲取,变成乙先手取内的数,由于总和固定且乙会按最优策略取,答案是
②同理,如果甲取,答案是
得到状态转移方程:
看着像是个区间,枚举长度后递推即可
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>a(n + 1), pre(n + 1);
for(int i = 1; i <= n; i++){
std::cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
std::vector<std::vector<int>>dp(n + 1, std::vector<int>(n + 1));
for(int i = 1; i <= n; i++){
dp[i][i] = a[i];
}
for(int len = 2; len <= n; len++){
for(int i = 1; i + len - 1 <= n; i++){
int j = i + len - 1;
dp[i][j] = std::max(dp[i][j], pre[j] - pre[i - 1] - std::min(dp[i + 1][j], dp[i][j - 1]));
}
}
std::cout << dp[1][n] << " " << pre[n] - dp[1][n] << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
10303 数字三角
用表示到达第行第列的最大路径和
对于每个位置,都有:
如果,
至于路径只需要倒着找一遍即可
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<std::vector<int>>a(n);
for(int i = 0; i < n; i++){
a[i].resize(i + 1);
for(int j = 0; j <= i; j++){
std::cin >> a[i][j];
}
}
std::vector<std::vector<int>>dp(n, std::vector<int>(n, 0));
dp[0][0] = a[0][0];
for(int i = 1; i < n; i++){
for(int j = 0; j <= i; j++){
dp[i][j] = std::max(dp[i][j], dp[i - 1][j] + a[i][j]);
if(j){
dp[i][j] = std::max(dp[i][j], dp[i - 1][j - 1] + a[i][j]);
}
}
}
std::vector<int>path;
int max = 0, r = n - 1, c;
for(int i = 0; i < n; i++){
if(dp[n - 1][i] >= max){
max = dp[n - 1][i];
c = i;
}
}
while(r > 0){
path.push_back(a[r][c]);
if(dp[r - 1][c] != dp[r][c] - a[r][c]){
c--;
}
r--;
}
path.push_back(a[0][0]);
std::cout << max << "\n";
for(int i = path.size() - 1; i >= 0; i--){
std::cout << path[i] << " \n"[i == 0];
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
10349 数字滑雪
设为以为起点的最长路径,然后往四个方向进行深搜
发现,有可能会访问到以前已经搜过的节点,此时不用继续搜索,直接相加后返回即可,这就是记忆化搜索
- 每个位置访问一遍,时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
constexpr int dx[] = {1, -1, 0, 0};
constexpr int dy[] = {0, 0, -1, 1};
void solve(){
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>>a(n, std::vector<int>(m));
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
std::cin >> a[i][j];
}
}
std::vector<std::vector<int>>dp(n, std::vector<int>(m));
auto dfs = [&] (auto &&self, int x, int y) -> int {
if(dp[x][y]){
return dp[x][y];
}
dp[x][y] = 1;
for(int i = 0; i < 4; i++){
int r = x + dx[i];
int c = y + dy[i];
if(r >= 0 && r < n && c >= 0 && c < m && a[r][c] < a[x][y]){
dp[x][y] = std::max(dp[x][y], 1 + self(self, r, c));
}
}
return dp[x][y];
};
int ans = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
ans = std::max(ans, dfs(dfs, i, j));
}
}
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
11077 最长公共子字符串
好像不是很好理解!
设两个字符串分别为,
若不为,则表示的前位和的前位在末尾对齐,且最后一位相同情况下的最长公共子串长度
若为表示末尾对齐时最后一位不匹配
这么解释的话,显然是只有时才进行状态转移(索引从开始)
则
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
std::string a, b;
std::cin >> a >> b;
int n = a.size(), m = b.size();
std::vector<std::vector<int>>dp(n + 1, std::vector<int>(m + 1));
int max = 0, inx = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[i - 1] != b[j - 1]){
continue;
}
dp[i][j] = dp[i - 1][j - 1] + 1;
if(dp[i][j] > max){
max = dp[i][j];
inx = i - dp[i][j];
}
}
}
std::string ans = a.substr(inx, max);
std::cout << max << "\n" << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
11078 不能移动的石子合并
环形的石子合并可以转化为一排的石子合并,我们往数组后面按序重新填充这个元素后做
逻辑和一排的情况相同
相当于是枚举所有个位置作为起点进行排形后取最优解
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>a(n + 1);
std::vector<i64>pre(2 * n + 1, 0);
for(int i = 1; i <= n; i++){
std::cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
for(int i = n + 1; i <= 2 * n; i++){
a.push_back(a[i - n]);
pre[i] = pre[i - 1] + a[i];
}
auto calc = [&](int len){
std::vector<std::vector<i64>>dp1(len + 1, std::vector<i64>(len + 1, INF));
std::vector<std::vector<i64>>dp2(len + 1, std::vector<i64>(len + 1, 0));
for(int i = 1; i <= len; i++){
dp1[i][i] = 0;
}
for(int i = len; i; i--){
for(int j = i + 1; j <= len; j++){
for(int k = i; k < j; k++){
dp1[i][j] = std::min(dp1[i][j], dp1[i][k] + dp1[k + 1][j] + pre[j] - pre[i - 1]);
dp2[i][j] = std::max(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + pre[j] - pre[i - 1]);
}
}
}
i64 ans1 = INF, ans2 = 0;
for(int i = 1; i + n - 1 <= len; i++){
ans1 = std::min(ans1, dp1[i][i + n - 1]);
ans2 = std::max(ans2, dp2[i][i + n - 1]);
}
std::cout << ans1 << " " << ans2 << "\n";
};
calc(n), calc(2 * n);
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
11080 游泳圈的最大子矩阵和
数据范围极小,可以直接枚举矩形的长度和高度
为了简化求和,先用二位前缀和预处理
由于是环形的,我们可以在每一行后按序填充这一行的个元素,然后在行以后重新按需填充行,形成一个的数组,然后枚举即可
- 复杂度
- 可以让行和列下标从开始,方便处理前缀和
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>>a(n + 1, std::vector<int>(m + 1));
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
std::cin >> a[i][j];
a[i].push_back(a[i][j]);
}
a.push_back(a[i]);
}
std::vector<std::vector<int>>pre(2 * n + 1, std::vector<int>(2 * m + 1));
for(int i = 1; i <= 2 * n; i++){
for(int j = 1; j <= 2 * m; j++){
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + a[i][j];
}
}
int ans = -inf;
for(int h = 1; h <= n; h++){
for(int w = 1; w <= m; w++){
for(int i = 1; i + h - 1 <= 2 * n; i++){
for(int j = 1; j + w - 1 <= 2 * m; j++){
int tmp = pre[i + h - 1][j + w - 1] - pre[i - 1][j + w - 1] - pre[i + h - 1][j - 1] + pre[i - 1][j - 1];
ans = std::max(ans, tmp);
}
}
}
}
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
11083 旅游背包
用表示体积为,重量为时能达到的最大价值
由于每个物品有多个,因此对于一个物品来说,可以进行次状态转移
状态转移方程:
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n, V, W;
std::cin >> n >> V >> W;
std::vector<int>v(n), w(n), c(n), t(n);
for(int i = 0; i < n; i++){
std::cin >> v[i] >> w[i] >> c[i] >> t[i];
}
std::vector<std::vector<int>>dp(V + 1, std::vector<int>(W + 1));
for(int i = 0; i < n; i++){
for(int k = 0; k < c[i]; k++){
for(int vv = V; vv >= v[i]; vv--){
for(int ww = W; ww >= w[i]; ww--){
dp[vv][ww] = std::max(dp[vv][ww], dp[vv - v[i]][ww - w[i]] + t[i]);
}
}
}
}
std::cout << dp[V][W] << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
11090 最大段乘积和最小段和
法一:动态规划
首先可以预处理出段表示的数,设为,由于数据范围很小,直接暴力即可
①最大段乘积:用表示前个位置划分出段,且第个位置属于第段的最大乘积
状态转移就是往前找第段的结束位置,
②最小段和:用表示前个位置划分出段,且第个位置属于第段的最小和
状态转移同样是往前找第段的结束位置,
至于找表达式,倒着找一遍即可
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n, m;
std::string s;
std::cin >> n >> m >> s;
s = " " + s;
std::vector<std::vector<i64>>num(n + 1, std::vector<i64>(n + 1));
for(int i = 1; i <= n; i++){
i64 cur = 0;
for(int j = i; j <= n; j++){
cur = cur * 10 + (s[j] - '0');
num[i][j] = cur;
}
}
std::vector<std::vector<i64>>dp1(n + 1, std::vector<i64>(m + 1, 0));
std::vector<std::vector<i64>>dp2(n + 1, std::vector<i64>(m + 1, INF));
dp1[0][0] = 1, dp2[0][0] = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= std::min(m, i); j++){
for(int k = 0; k < i; k++){
dp1[i][j] = std::max(dp1[i][j], dp1[k][j - 1] * num[k + 1][i]);
dp2[i][j] = std::min(dp2[i][j], dp2[k][j - 1] + num[k + 1][i]);
}
}
}
std::cout << dp1[n][m] << " " << dp2[n][m] << "\n";
std::vector<i64>path1, path2;
int inx = n, x = m;
while(x){
int nxt;
for(int i = x - 1; i < inx; i++){
if(dp1[i][x - 1] * num[i + 1][inx] == dp1[inx][x]){
nxt = i;
break;
}
}
path1.push_back(num[nxt + 1][inx]);
inx = nxt, x--;
}
std::reverse(path1.begin(), path1.end());
inx = n, x = m;
while(x){
int nxt;
for(int i = x - 1; i < inx; i++){
if(dp2[i][x - 1] + num[i + 1][inx] == dp2[inx][x]){
nxt = i;
break;
}
}
path2.push_back(num[nxt + 1][inx]);
inx = nxt, x--;
}
std::reverse(path2.begin(), path2.end());
for(int i = 0; i < m; i++){
std::cout << path1[i] << "*="[i == m - 1];
}
std::cout << dp1[n][m] << "\n";
for(int i = 0; i < m; i++){
std::cout << path2[i] << "+="[i == m - 1];
}
std::cout << dp2[n][m] << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
法二:搜索
数据范围只有,当然可以搜索
首先确定参数:我们需要知道前面哪些位置已经被用掉了,所以需要索引;要知道当前的和/积;还要知道前面已经划分出了多少段
确定参数后就是返回条件的确定:最多划分段,在最后一段划分的时候就应该更新答案并且返回了
进一步地,如果第段已经划分完了,剩下部分必然属于最后一段,于是可以直接在划分出段就更新答案并且返回
下面的代码中是当前段开始的索引,是当前已有的和/积,是在这个位置前划分出了几段
- 时间复杂度:首先预处理部分是的,搜索部分是划分出个非空段,用隔板法计算,一共有种,每次拷贝的开销都是,共,总复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n, m;
std::string s;
std::cin >> n >> m >> s;
std::vector<std::vector<i64>>num(n, std::vector<i64>(n));
for(int i = 0; i < n; i++){
i64 cur = 0;
for(int j = i; j < n; j++){
cur = cur * 10 + (s[j] - '0');
num[i][j] = cur;
}
}
std::vector<i64>path1, path2, p;
i64 max = -1, min = INF;
auto dfs1 = [&](auto &&self, int inx, i64 cur, int hav) -> void {
if(hav == m - 1){
if(cur * num[inx][n - 1] > max){
max = cur * num[inx][n - 1];
path1 = p;
path1.push_back(num[inx][n - 1]);
}
return;
}
for(int i = inx; n - i >= m - hav; i++){
p.push_back(num[inx][i]);
self(self, i + 1, cur * num[inx][i], hav + 1);
p.pop_back();
}
};
dfs1(dfs1, 0, 1, 0);
auto dfs2 = [&](auto &&self, int inx, i64 cur, int hav) -> void {
if(hav == m - 1){
if(cur + num[inx][n - 1] < min){
min = cur + num[inx][n - 1];
path2 = p;
path2.push_back(num[inx][n - 1]);
}
return;
}
for(int i = inx; n - i >= m - hav; i++){
p.push_back(num[inx][i]);
self(self, i + 1, cur + num[inx][i], hav + 1);
p.pop_back();
}
};
dfs2(dfs2, 0, 0, 0);
std::cout << max << " " << min << "\n";
for(int i = 0; i < m; i++){
std::cout << path1[i] << "*="[i == m - 1];
}
std::cout << max << "\n";
for(int i = 0; i < m; i++){
std::cout << path2[i] << "+="[i == m - 1];
}
std::cout << min << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
8595 钱币组合问题
多重背包问题,相较背包多了数量上的限制
- 注意循环的顺序问题,寻找构成方法数其实相当于是找组合数而不是排列数
- 先拿一张分再拿两张分和一次拿三张分是同种情况!
- 时间复杂度,最劣
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>v(n), k(n);
for(int i = 0; i < n; i++){
std::cin >> v[i];
}
for(int i = 0; i < n; i++){
std::cin >> k[i];
}
int m;
std::cin >> m;
std::vector<int>dp1(m + 1), dp2(m + 1, inf);
dp1[0] = 1, dp2[0] = 0;
for(int i = 0; i < n; i++){
for(int j = m; j > 0; j--){
for(int t = 1; t <= k[i]; t++){
if(j < t * v[i]){
break;
}
dp1[j] += dp1[j - t * v[i]];
}
}
}
for(int i = 0; i < n; i++){
for(int t = 1; t <= k[i]; t++){
for(int j = m; j >= v[i]; j--){
dp2[j] = std::min(dp2[j], dp2[j - v[i]] + 1);
}
}
}
if(!dp1[m]){
std::cout << 0 << "\n" << "no possible\n";
}else{
std::cout << dp1[m] << "\n" << dp2[m] << "\n";
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
17089最大子段和
好像很困难!
令表示到第位置,已经划分出段,且属于第段时的最大子段和
①如果第位置时已经划分出了段,那么直接加入,即为
②如果是在位置才开始第段呢,此时维护一个长度为的数组,用于表示划分出了段时的最大子段和,那么有
于是得到状态转移方程:
当原数组中非正数时,可以让答案和取
- 时间复杂度
- 该题数据极弱,实际数据不超过,不超过,写成都能过
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n, m;
std::cin >> n >> m;
std::vector<int>a(n);
int cnt = 0;
for(int i = 0; i < n; i++){
std::cin >> a[i];
cnt += (a[i] <= 0);
}
std::vector<std::vector<int>>dp(n, std::vector<int>(m + 1, -inf));
std::vector<int>pre(m + 1, -inf);
dp[0][1] = pre[1] = a[0];
for(int i = 1; i < n; i++){
for(int j = 1; j <= std::min(i + 1, m); j++){
dp[i][j] = std::max(dp[i - 1][j] + a[i], pre[j - 1] + a[i]);
}
for(int j = 1; j <= std::min(i + 1, m); j++){
pre[j] = std::max(pre[j], dp[i][j]);
}
}
int ans = -inf;
for(int i = m - 1; i < n; i++){
ans = std::max(ans, dp[i][m]);
}
if(cnt >= m){
ans = std::max(ans, 0);
}
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
17098 广告牌最佳安放问题
首先按照大小对广告牌位置排序,然后维护一个和当前广告牌距离的最大值,每更新一个位置双指针一下即可
实际上给的数据都是有序的,不用排序,那直接双指针跑一遍即可
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int m, n;
std::cin >> m >> n;
std::vector<int>x(n), r(n);
for(int i = 0; i < n; i++){
std::cin >> x[i];
}
for(int i = 0; i < n; i++){
std::cin >> r[i];
}
std::vector<int>dp(n);
int max = 0;
for(int i = 0, j = 0; i < n; i++){
while(x[i] - x[j] > 5){
max = std::max(max, dp[j]);
j++;
}
dp[i] = max + r[i];
}
int ans = *std::max_element(dp.begin(), dp.end());
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
17099 周工作计划安排
- 他这里应该是默认第周不能接高压工作
用表示第周接低压工作的最大收益,表示第周接高压工作的最大收益
容易得到状态转移方程:
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>l(n + 1), h(n + 1);
for(int i = 1; i <= n; i++){
std::cin >> l[i];
}
for(int i = 1; i <= n; i++){
std::cin >> h[i];
}
std::vector<std::vector<int>>dp(n + 1, std::vector<int>(2));
dp[1][0] = l[1];
for(int i = 2; i <= n; i++){
dp[i][0] = std::max(dp[i - 1][0], dp[i - 1][1]) + l[i];
dp[i][1] = std::max(dp[i - 2][0], dp[i - 2][1]) + h[i];
}
std::cout << std::max(dp[n][0], dp[n][1]) << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
17102 "一条路径图"的最大独立集问题
令表示范围内可达到的最大总权
正着递推的话,第个位置实际只受到前一个位置的限制
考虑将第个位置的权值加入总权,那么就是
但是由于表示范围内的最大总权,我们还需考虑不加入第个位置的权重时的最大总权
为
得到状态转移方程:
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int n;
std::cin >> n;
std::vector<int>w(n + 1);
for(int i = 1; i <= n; i++){
std::cin >> w[i];
}
std::vector<i64>dp(n + 1, -INF);
dp[0] = 0, dp[1] = std::max(w[1], 0);
for(int i = 2; i <= n; i++){
dp[i] = std::max(1LL * w[i], dp[i - 2] + w[i]);
dp[i] = std::max(dp[i], dp[i - 1]);
}
i64 ans = *std::max_element(dp.begin(), dp.end());
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/
19187 广告牌最佳安放问题(二)
都能达到,朴素的的做法会超时,考虑优化掉一个
令表示在第个位置安放第个广告牌的的最大收益,现在我们需要关注的是第块广告牌安放的位置
不妨建一个长度为的数组,用于表示距离当前位置大于五公里时,已经安放了块广告牌的最大收益
得到状态转移方程:
数组可通过双指针法进行更新
- 时间复杂度
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using pii = std::pair<int,int>;
using pll = std::pair<i64,i64>;
using pli = std::pair<i64,int>;
using pil = std::pair<int,i64>;
constexpr i64 INF = 0x3f3f3f3f3f3f3f3fll;
constexpr int inf = 0x3f3f3f3f;
void solve(){
int T, n, m;
std::cin >> T >> n >> m;
std::vector<int>x(n), r(n);
for(int i = 0; i < n; i++){
std::cin >> x[i];
}
for(int i = 0; i < n; i++){
std::cin >> r[i];
}
std::vector<int>pre(m + 1);
std::vector<std::vector<int>>dp(n, std::vector<int>(m + 1));
for(int i = 0, j = 0; i < n; i++){
while(x[i] - x[j] > 5){
for(int k = 0; k <= m; k++){
pre[k] = std::max(pre[k], dp[j][k]);
}
j++;
}
for(int k = 1; k <= std::min(i + 1, m); k++){
dp[i][k] = pre[k - 1] + r[i];
}
}
int ans = 0;
for(int i = m - 1; i < n; i++){
ans = std::max(ans, dp[i][m]);
}
std::cout << ans << "\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef LOCAL
freopen("make.txt", "r", stdin);
freopen("a.txt", "w", stdout);
#endif
int T = 1;
// std::cin >> T;
while(T--){
solve();
}
return 0;
}
/*
*/