「AGC040E」Prefix Suffix Addition 题解 (DP)
题目简介
你有一个长为 的序列 ,一开始全部为 0,你现在可以以任意顺序进行任意次以下两种操作:
- 选定整数 与不下降非负序列 ,对所有 ,令 加上 。
- 选定整数 与不上升非负序列 ,对所有 ,令 加上 。
问最少进行多少次操作使得最后对任意 有 。
转化
0x1.
由于 的初始值全为 ,所以可以考虑将其翻一转,变成
- 选定整数 与不上升非负序列 ,对所有 ,令 加上 。
- 选定整数 与不下降非负序列 ,对所有 ,令 加上 。
这样,对于操作1,可视为
对于操作2,可将赋值为,然后令
操作1的所有序列叠加,所得的还是不上升非负序列,记为
操作2的所有序列叠加,所得的还是不下降非负序列,记为
可得 ,转换得 ,已知,DP求 即可。
0x2.
令 表示 DP 到第 个位置,其中 的最小值。
由:
不难得出状态转移方程:
变形:
0x3.
由上式可知, 越大,越难满足 和 。
也就是说, 越小,越容易满足 和 。
另外,对于已知状态, 只可能有三种值:
换句话说,如果对于 ,有 ,那么 是无意义的。
0x4
偷换概念:
使用 表示,当DP到前 位,最小值为 时对应的 最小值。
即 将原本的概念调换一下。
使用map
进行DP。
#include<cstdio>
#include<iostream>
#include<map>
#include<vector>
#include<functional>
int main(){
int n;std::cin>>n;
std::vector<int>a(n+1);
std::map<int,int>f[2];
int cur=0;
for(int i=1;i<=n;++i)std::cin>>a[i];
std::function<void(int,int,int)>update=
[&](int k,int x,int y){
if(!f[k].count(x))f[k][x]=y;
f[k][x]=std::min(f[k][x],y);
};
f[cur][0]=a[1],f[cur][1]=0;
for(int i=2;i<=n;++i){
cur^=1;f[cur].clear();
int v1=std::max(a[i]-a[i-1],0);
int v2=std::min(a[i]-a[i-1],0);
for(auto it=f[cur^1].begin();it!=f[cur^1].end();++it){
if(it->first-f[cur^1].begin()->first>2)continue;
int x=it->first;
int y=it->second;
if(v1+y<=a[i])update(cur,x,std::max(v1+y,0));
if(v2+y<=a[i])update(cur,x+1,std::max(v2+y,0));
if(v2+y>0)update(cur,x+2,0);
}
}
int ans=n;
for(auto it=f[cur].begin();it!=f[cur].end();++it)
ans=std::min(ans,it->first+(it->second>0));
std::cout<<ans<<'\n';
return 0;
}
分类:
AtCoder
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通