本周是DP周,自从贪心周开始之后,大部分的题都需要自己寻找,解决,思维难度逐渐增高,很少出现模版题,没有模版可以背,在本周看博客的过程中最能明显的感觉到的就是思维的重要性。
在我看来DP的核心就是发现一个最优结构使得当前的结果可以用之前计算过的结果表示,这与贪心不同,动态规划将每一个子问题的解都存储起来,并且在顺序求解字问题的过程中前一个子问题会对当前的问题有影响。也因此动态规划适用于:可存在多个子问题且整体问题最优解中每个子问题也都是最优解,每个问题的决策只会影响到本次决策。
当然在最开始的几天我还是找了几个普及-的水题练练手,熟悉一下
看题可知:1s的时间意味着不能暴力求解。设置dp[i][j]; 其中i代表第几盘菜,j代表花费。dp[i][j]存着就是上一个最大买的种类。初始肯定是0,因为很有可能它一个也买不起。
我们通过分析可知:如果选择这第i盘菜的话,那么我们需要判断第i盘菜的价格ai是否比目前的花费做比较。如果价格大于花费,很明显它买不起,那么他的最大次数则是上次的最大种类数。
#include<iostream>
using namespace std;
int a[107];
int dp[107][10007];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j>a[i]) dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]];
else if(j==a[i]) dp[i][j]=dp[i-1][j]+1;
else if(j<a[i]) dp[i][j]=dp[i-1][j];
}
}
cout<<dp[n][m]<<endl;
}
HDU 4960 Another OCD Patient
题意:
给定一个n长的序列vi,现在要求合并连续子序列,使得最终的序列式一个回文序列。每次合并i长的子序列,需要花费ai。求最小花费。
这道题找原题找半天。。
求回文序列,前第n个与后第n个应该是相等的,开头和结尾要合并成相同的值,抛去已经合并的开头和结尾,剩下的又要合并相同的值,如此反复知道这个序列都变成了回文,这题的状态是什么, 其实只需要知道当前区间就好了。即我把头尾合并完了是回文的了, 利用两个指针找下两个位置让他们相同,可以选这个位置,也可以不选,用记忆化搜索写起来比较方便,每次对于求的L和R,枚举i,j,使得 L-i合并之后可以与j-R合并之后回文,然后递归处理i和j即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e3 + 5;
const ll INF = 1e18;//利用宏定义使代码更简单
ll sum[maxn], v[maxn];
int cost[maxn], dp[maxn][maxn], n;
int dfs(int l, int r)
{
if(dp[l][r] != -1) return dp[l][r];
int ans = cost[r-l+1];
for(int i = l, j = r; i < j;)
{
ll sum1 = sum[i]-sum[l-1];
ll sum2 = sum[r]-sum[j-1];
if(sum1 == sum2)
ans = min(ans, dfs(i+1, j-1) + cost[i-l+1] + cost[r-j+1]), i++, j--; //这还是很通俗易懂把,两个指针把所有情况都枚举到了
else if(sum1 < sum2) i++;
else j--;
}
return dp[l][r] = ans;
}
int main()
{
while(cin>>n)
{
memset(dp, -1, sizeof(dp));
memset(sum, 0, sizeof(sum));
for(int i = 1; i <= n; i++)
{cin>>v[i]; sum[i] = sum[i-1] + v[i];}
for(int i = 1; i <= n; i++)
cin>>cost[i];
cout<<dfs(1, n)<<endl;
}
return 0;
}
CodeForces 687C - The Values You Can Make
题意:给定n(1 <= n <= 500)个硬币,每个硬币都有面值,求每个能构成和为k(1 <= k <= 500)的硬币组合中,任意个数相互求和的总额种类,然后将所有硬币组合中最后得到的结果输出。
这道题直接看的大佬代码,但还是不太会。只大致理解了思路
一维表示总额,二维表示能否由一维的总额得出此额度。
假如枚举到的硬币面值为 t ,如果存在dp[ i - t ] [ j ] = true,那么有
1、dp[ i ] [ j ] = true; 相当于总额里增加一个 t 的面值的硬币,但实际组成 j 的额度时并没有用到它。
2、dp[ i ] [ j + t ] = true; 相当于总额里增加一个 t 的面值的硬币,并且用它构成了新达到的额度 j + t。
初始化dp[ 0 ] [ 0 ] = true; 总额为 0 时,能构成 0 额度。
为了防止二次利用此硬币,总额需从 k 递减至 t。(否则的话若从 t 递增至 k ,条件判断时会重复利用刚刚由此面值得到的新额度,即刚刚使用此硬币标为true的项,造成此硬币二次利用)
最后只需要统计总额为 k 时能达到的额度,并且输出即可。
最后总结一下吧,这周基本看的全是题,博客也就全写题了,这类问题其实我感觉就是纯积累,看的多了,做的多了,自然就能有思路。。
下周继续加油吧,