区间DP,数位DP
dp(动态规划)顾名思义便是动态的一种规划,而这种规划往往会跟状态,状态转移方程,记忆化搜索扯上关系,当然DP也是各个OI考试的必考点和常考点,在毒瘤出题人的折磨下,出现了许许多多的动态规划,有线性,背包,环形,插头,区间,数位,状压等等各种动态规划,最近刚刚吧区间和数位DP学会。
区间DP:一看就知道肯定与区间有关,这类DP主要处理一些区间的最值和各种状态,貌似ST表就相当于区间DP,说到区间DP,就不得不提一个题——石子合并。
题目:
我们乍一看似乎可以贪心,但是这个题跟合并果子可是万万不同,合并果子是一道堆,而这道题还是环形,因此我们看题一定要看清楚,不然就会GG。我们要先化环为链。
我们难道做DP就要先想这是什么DP吗,错了,我们一定要先想状态和状态转移方程,慢慢慢慢就会知道这是什么DP了。再看这道题,我们先处理前缀和,这样好控制一段区间里的所有石子的值,到合并时,肯定要加上这段区间里的所有值,因此我们要处理前缀和,方便合并。
这样的话我们可以轻易的写出状态转移方程:
dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]);//最小值 dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);//最大值
看这个状态转移方程,如果你写出了状态转移方程也不要大意,我们看i表示左端点,而j表示右端点,k表示中间断点。而显而易见,k一定>i,<j.
又因为原状态转移方程中出现了k+1,所以我们应该在枚举的时候,让i从2*n-1到1。
这样这个题就很好写了。
代码:
#include<iostream> #include<cstdio> using namespace std; int n,minn=0x7ffffff,maxn; int data[10000],dp1[1000][2000],dp2[1000][2010],sum[10000];//sum表示前缀和,dp1,dp2,分别表示最大最小值。 int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>data[i]; data[i+n]=data[i];//环形 } for(int i=1;i<=n*2;i++) sum[i]=sum[i-1]+data[i]; for(int i=n*2-1;i>0;i--)//因为是环形,所以要从n*2-1开始 { for(int j=i+1;j<i+n;j++) { dp1[i][j]=0x3f3f3f; for(int k=i;k<j;k++) { dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]); dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]); } } } for(int i=1;i<=n;i++) { minn=min(minn,dp1[i][i+n-1]); maxn=max(maxn,dp2[i][i+n-1]); } printf("%d\n%d\n",minn,maxn); return 0; }
这个题也是一道很经典的题了,从此我们可以得出区间DP的一个普遍规律(仅供参考,每个DP都不一样,所以不要照着葫芦画葫芦)。
我们要先枚举左端点,然后枚举区间长度或右端点,然后枚举中间断点用于状态转移方程。
数位DP:
不得不说数位DP还是挺难的,到底有多难呢,NOIP不考(为了长远打算)
https://wenku.baidu.com/view/9de41d51168884868662d623.html
数位DP的题目特点是让你求一段区间内的满足一个特点的数的个数,我们首先看这些数有什么特点,然后枚举看这个数的第一位是否<=他给出的条件,如果满足就可以,如果第一位恰好在边界上,那我们就不能随便枚举了,这时候我们就应该再判断下一位,这样依次判断直到满足或不满足。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步