一些dp题目
一些常见的线性 \(dp\),处理方式类似
C. Boring Day
\(\hspace{20px}\)Egor 有一副 \(n\) 的扑克牌,最上面的 \(i\) 张牌上写着数字 \(a_i\) 。Egor 想玩一定轮数的游戏,直到牌用完为止。在每一轮中,他都会从牌面顶端抽取一张非零数的牌,然后结束这一轮。如果在这一轮中收集到的纸牌上的数字之和在 \(l\) 和 \(r\) 之间(包括首尾数字),则这一轮获胜;否则,这一轮失败。
\(\hspace{20px}\)Egor 对纸牌的顺序了如指掌。请帮助 Egor 确定他在这样的游戏中最多可以赢多少局。请注意,Egor 不需要连续获胜。
\(\hspace{20px}\)\(1 \le n \le 10^5,1 \le l \le r \le 10^9,1 \le a_i \le 10^9\)。
\(\hspace{20px}\)Link
动态规划
选或不选,如果选,就跳到下一个 \(sum\) 在 \(l\) 和 \(r\) 之间的位置。
对于 \(sum\) 的处理,我们可以用前缀和,对于每个位置,我们用二分查找下一个满足条件的最小位置,如果该位置存在,就跳过去寻找。
正向 dp
点击查看代码
void solve(){
int n, L, R;
cin >> n >> L >> R;
vector<long long> a(n + 1), pre(n + 1, 0), dp(n + 1);
dp[0] = 0;
for(int i = 1;i <= n;i++){
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
for(int i = 1;i <= n;i++){
int l = 1, r = i, tmp = -1;
while(l <= r){
int mid = (l + r) >> 1;
long long sum = pre[i] - pre[mid - 1];
if(L <= sum && sum <= R){
tmp = mid;
l = mid + 1;
}else if(sum <= L){
r = mid - 1;
}else{
l = mid + 1;
}
}
dp[i] = dp[i - 1];
if(tmp != -1){
dp[i] = max(dp[i], dp[tmp - 1] + 1);
}
}
cout << dp[n] << "\n";
}
反向 dp
点击查看代码
void solve(){
int n, L, R;
cin >> n >> L >> R;
vector<long long> a(n + 1), pre(n + 1, 0), dp(n + 2);
dp[n + 1] = 0;
for(int i = 1;i <= n;i++){
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
for(int i = n;i >= 1;i--){
int l = i, r = n, tmp = -1;
while(l <= r){
int mid = (l + r) >> 1;
long long sum = pre[mid] - pre[i - 1];
if(L <= sum && sum <= R){
tmp = mid;
r = mid - 1;
}else if(sum <= L){
l = mid + 1;
}else{
r = mid - 1;
}
}
dp[i] = dp[i + 1];
if(tmp != -1){
dp[i] = max(dp[i], dp[tmp + 1] + 1);
}
}
cout << dp[1] << "\n";
}
双指针 + 贪心
---------------------- 分割线 ----------------------
D. Kousuke's Assignment
\(\hspace{20px}\)老师给了他一个由 \(n\) 个整数组成的数组 \(a\) ,要求他计算数组 \(a\) 中不重叠的段的数量,这样每一段都被认为是美丽的。
\(\hspace{20px}\)如果有 \(a_l + a_{l+1} + \dots + a_{r-1} + a_r=0\) 段 \([l,r]\) 被认为是美丽的。
\(\hspace{20px}\)对于一个固定的数组 \(a\) ,你的任务是计算不重叠的美丽线段的最大数目。
\(\hspace{20px}\)\(1 \le n \le 10^5,-10^5 \le a_i \le 10^5\)。
\(\hspace{20px}\)Link
动态规划
选或不选,如果选,就跳到从以该点开始的美丽线段的结尾。
对于美丽线段的处理,我们可以用前缀和,对于每个位置,如果前缀和 \(pre_l = pre_r\),有从 \(l \dots r - 1\) 或 \(l + 1 \dots r\) 位置的和为 \(0\),我们预处理时记录下一个满足条件的最小位置 \(lst\),如果该位置存在,就跳过去寻找。
对于数组只有 \(0\) 元素的处理:提前预处理 \(pos_0\) 为 \(0\)。
Fun fact : 注意别爆 int 了,题解给的代码爆了=)。
点击查看代码
void solve(){
int n;
cin >> n;
vector<long long> a(n + 1), pre(n + 1, 0);
vector<int> lst(n + 1, -1);
map<long long, int> pos;
pos[0] = 0;
for(int i = 1;i <= n;i++){
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
if(pos.find(pre[i]) != pos.end()){
lst[i] = pos[pre[i]];
}
pos[pre[i]] = i;
}
vector<int> dp(n + 1);
dp[0] = 0;
for(int i = 1;i <= n;i++){
dp[i] = dp[i - 1];
if(lst[i] != -1){
dp[i] = max(dp[i], 1 + dp[lst[i]]);
}
}
cout << dp[n] << "\n";
}
---------------------- 分割线 ----------------------
C. Against the Difference
\(\hspace{20px}\)我们定义块是指数组中所有元素的长度都等于数组长度的数组。例如, \([3, 3, 3]\) 、 \([1]\) 和 \([4, 4, 4, 4]\) 是数块,而 \([1, 1, 1]\) 和 \([2, 3, 3]\) 不是数块。
\(\hspace{20px}\)如果一个数组可以由任意数量的数块(可能为零)连接而成,那么这个数组就被称为整齐数组。请注意,空数组总是整齐的。
\(\hspace{20px}\)给你一个由 \(n\) 个整数组成的数组 \(a\) 。求其最长整齐子序列的长度。
\(\hspace{20px}\)\(1 \le n \le 10^5,1 \le a_i \le n\)。
\(\hspace{20px}\)Link
动态规划
选或不选,如果选,就跳到从以该点开始的块的结尾。
对于块的处理,观察到 \(a_i\) 的范围,我们可以用二维数组(类似邻接表)记录,预处理时,每个数字都把下标 存进去,顺便存个个数 \(cnt\)。更新时,\(cnt\) 也一起更新,如果个数足够,就跳过去更新 \(dp\) 答案。
正向 dp
点击查看代码
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
a[i]--;
}
vector<vector<int>> vec(n);
vector<int> id(n);
for (int i = 0; i < n; i++) {
id[i] = vec[a[i]].size();
vec[a[i]].push_back(i);
}
vector<int> dp(n + 1);
for (int i = 0; i < n; i++) {
dp[i + 1] = dp[i];
if (id[i] >= a[i]) {
dp[i + 1] = max(dp[i + 1], a[i] + 1 + dp[vec[a[i]][id[i] - a[i]]]);
}
}
cout << dp[n] << "\n";
}
反向 dp
点击查看代码
void solve(){
int n;
cin >> n;
vector<int> a(n);
vector<int> dp(n + 1, INT_MAX);
vector<int> cnt(n + 1, 0);
vector<vector<int>> pos(n + 1);
for(int i = 0;i < n;i++){
cin >> a[i];
cnt[a[i]]++;
pos[a[i]].push_back(i);
}
dp[n] = 0;
for(int i = n - 1;i >= 0;i--){
cnt[a[i]]--;
dp[i] = dp[i + 1];
if(cnt[a[i]] + a[i] <= pos[a[i]].size()){
dp[i] = max(dp[i], dp[pos[a[i]][cnt[a[i]]+a[i]-1] + 1] + a[i]);
}
}
cout << dp[0] << "\n";
}
浙公网安备 33010602011771号