要不要开二维?就问自己:“如果有两个人同时走到第 i 步,但他们之前的经历不同,这种不同会限制他们接下来的选择吗?”
能不能压缩?用滚动数组来代替二维,我们只考虑会影响结果的值。只记我们需要记的,过期的扔掉。
DP原理
数字三角形
用途:从顶到底或底到顶,求最大路径和。 常见坑:从底到顶递推可省去边界处理。
1 2 3 4 5 6 7 8 9 10 11 12
voidsolve(){ int n; cin >> n; int sjx[100][100]; for (int i = 0; i < n; i++) for (int j = 0; j <= i; j++) cin >> sjx[i][j]; for (int i = n-2; i >= 0; i--) for (int j = 0; j <= i; j++) sjx[i][j] += max(sjx[i+1][j], sjx[i+1][j+1]); cout << sjx[0][0] << '\n'; }
复杂度:O(n²),空间 O(1)(直接在输入数组上修改)。
2.5 记忆化搜索
用途:从未知到已知递推,适合复杂状态转移。 与 DP 区别:
DP:从已知到未知,严格顺序,循环实现,常数小。
记忆化搜索:从未知到已知,递归实现,需状态数组防重复计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
vector<int> mama(100230, -1); intdfs(int a){ if (a == 1) return1; if (mama[a] != -1) return mama[a]; int cnt = 1; for (int i = 1; i <= a / 2; i++) cnt += dfs(i); mama[a] = cnt; return cnt; } voidsolve(){ int n; cin >> n; cout << dfs(n) << '\n'; }
复杂度:O(n²),空间 O(n)(状态数)。
线性DP
最大子段和
P1115 最大子段和
题目描述
给一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai。
输出格式
输出一行一个整数表示答案。
数据规模与约定
对于 40% 的数据,保证 n≤2×103。
对于 100% 的数据,保证 1≤n≤2×105,−104≤ai≤104。
AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voidsolve() { int n; cin>>n; vi nums(n+1);
for (int i=1;i<=n;i++) cin>>nums[i]; vi dp(n+1); int ans=-4545556565; for(int j=1;j<=n;j++) { dp[j]=dp[j-1]+nums[j]; if (dp[j]<=0) dp[j]=nums[j]; ans=max(ans,dp[j]); } cout<<ans<<'\n'; }
A. Against the Difference
题目
We define that a block is an array where all elements in it are equal to the length of the array. For example, [3,3,3], [1], and [4,4,4,4] are blocks, while [1,1,1] and [2,3,3] are not.
An array is called neat if it can be obtained by the concatenation of an arbitrary number of blocks (possibly zero). Note that an empty array is always neat.
You are given an array a consisting of n integers. Find the length of its longest neat subsequence∗. ∗A sequence c is a subsequence of a sequence a if c can be obtained from a by the deletion of several (possibly, zero or all) element from arbitrary positions.
我们定义block是一个数组,其中的所有元素都等于该数组的长度。例如, [3,3,3] , [1] 和 [4,4,4,4] 是块,而 [1,1,1] 和 [2,3,3] 不是块。
如果数组可以通过任意数量的块(可能为零)的连接获得,则称为整齐数组。注意,空数组总是整洁的。
给定一个由 n 个整数组成的数组 a 。求它的最长整洁子序列 ∗ 的长度。 ∗ 序列 c 是序列 a 的子序列,如果 c 可以从 a 中删除任意位置的几个(可能为零或全部)元素。
voidsolve() { int n; cin>>n; vi nums(n+1); vector<vi> idx(n+1); vi ery(n+1); rep(i,1,n) { cin>>nums[i]; idx[nums[i]].push_back(i); ery[i]=idx[nums[i]].size(); }
vi dp(n+1); dp[0]=0; vi xz(n+1); for(int i=1;i<=n;i++) { int pans=0; int cur=ery[i]-nums[i]; if(cur>=xz[i]*nums[i]) { pans=dp[idx[nums[i]][cur]-1]+nums[i]; xz[nums[i]]++; } dp[i]=max(pans,dp[i-1]);
vi dp(32,0); int zuida=-1; for (int hsh : nums) { for (int i = 0; i < 32; i++) { if ((hsh >> i) & 1) { zuida=max(dp[i]+1,zuida); } } for (int i = 0; i < 32; i++) { if ((hsh >> i) & 1) { dp[i]=zuida; } } }
voidsolve(){ int n, v; cin >> n >> v; structkind { int value, weight; }; vector<kind> nums(n); for (int i = 0; i < n; i++) { cin >> nums[i].weight >> nums[i].value; } vector<int> dp(v + 1); vector<int> dp2(v + 1, -99999999); // 恰好装满 dp2[0] = 0; for (int i = 0; i < n; i++) { for (int j = v; j >= nums[i].weight; j--) { dp[j] = max(dp[j], dp[j - nums[i].weight] + nums[i].value); dp2[j] = max(dp2[j], dp2[j - nums[i].weight] + nums[i].value); } } cout << dp[v] << '\n'; cout << (dp2[v] > 0 ? dp2[v] : 0) << '\n'; }
二维数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voidsolve(){ int t, m; // t=容量, m=物品数 cin >> t >> m; structcy { int time, ac; }; vector<cy> cord(m + 1); for (int i = 1; i <= m; i++) { cin >> cord[i].time >> cord[i].ac; } int dp[105][1005] = {}; for (int i = 1; i <= m; i++) { for (int j = 0; j <= t; j++) { if (j >= cord[i].time) dp[i][j] = max(dp[i-1][j-cord[i].time] + cord[i].ac, dp[i-1][j]); else dp[i][j] = dp[i-1][j]; } } cout << dp[m][t] << '\n'; }
复杂度:
一维:O(n*v),空间 O(v)。
二维:O(nt),空间 O(nt)。
完全背包
用途:物品可无限选,求最大价值。 常见坑:正序循环(区别于 01 背包倒序),防止重复选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
voidsolve(){ int time, n; cin >> time >> n; structcy { int t, v; }; vector<cy> hsh(n + 1); for (int i = 1; i <= n; i++) cin >> hsh[i].t >> hsh[i].v; vector<int> dp(time + 1); for (int i = 1; i <= n; i++) { for (int j = hsh[i].t; j <= time; j++) dp[j] = max(dp[j - hsh[i].t] + hsh[i].v, dp[j]); } cout << dp[time] << '\n'; }