线性dp,背包问题,区间dp,树形dp,环形与后效性处理,状压dp,计数类dp,数位dp,倍增优化,数据结构优化,单调队列优化,斜率优化,四边形不等式
>
从集合角度考虑dp问题:
|
|
技巧
在设计状态转移方程时,不一定要以“如何计算出一个状态”的形式给出,也可以考率“一个已知状态应该更新哪些后续阶段的未知状态”(即从某状态的入边与出边综合考虑)
在实现状态转移方程时,要注意观察决策集合的范围随着状态的变化情况。对于“决策集合中的元素只增多不减少”的情景,可以用一个临时变量来标记决策集合的当前信息,避免重复扫描
|
|
求解线性dp问题,先确定阶段,若阶段不足以表示一个状态,则可以把所需的附加信息也作为状态的维度
在确定dp状态时,要选择最小的能覆盖整个状态空间的“维度集合”,若dp状态由多个维度构成,则应检查这些维度之间能否相互导出,用尽量少的维度覆盖整个状态空间,排除冗余维度
输出方案:从终态往前推即可
|
|
有时可以通过额外的算法确定dp状态的计算顺序,如贪心排序等,以确保无后效性
有时可在状态空间中运用等效手法对状态进行缩放
|
|
戳这里
一般表示:f[i][j]-区间[i,j]的属性
|
|
在设置区间断点k时,一定要注意是否会出现k不同而子区间相同的情况,即决策的互斥性
任意选择一个位置断开,复制成2倍长度的链”的方法,是解决dp中环形结构的常用手段之一
|
|
对于计数类的dp问题,通常一个状态的各个决策之间满足“加法原则”,而每个决策划分的几个子状态之间满足“乘法原则”。在设计状态转移方程的决策方式与划分方法时,一个状态的所有决策之间必须具有互斥性
|
|
对于每个节点x,先递归在它的每个子节点进行dp,回溯时,从子节点向节点x进行状态转移。
|
|
二次扫描换根dp:这种问题一般出现在无根树确定根节点。一般先子下而上进行树形dp,然后对整棵树进行一次dfs,自上而下进行换根计算。
|
|
- 环形
- 在某个星球上,一天由 N 个小时构成,我们称0点到1点为第1个小时、1点到2点为第2个小时,以此类推。
在第 i 个小时睡觉能够恢复Ui点体力。
在这个星球上住着一头牛,它每天要休息B个小时。
它休息的这B个小时不一定连续,可以分成若干段,但是在每段的第一个小时,它需要从清醒逐渐入睡,不能恢复体力,从下一个小时开始才能睡着。
为了身体健康,这头牛希望遵循生物钟,每天采用相同的睡觉计划。
另外,因为时间是连续的,即每一天的第N个小时和下一天的第1个小时是相连的(N点等于0点),这头牛只需要在每N个小时内休息够B个小时就可以了。
请你帮忙给这头牛安排一个睡觉计划,使它每天恢复的体力最多。- 此题将环中n(0)和1间断开,分别对n(0)和1点没有睡觉和正在睡觉的情况做dp,取二者最优值.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
//https://www.acwing.com/activity/content/code/content/171286/ int n,m; int f[2][N][10],w[N]; int main(){ scanf("%d%d",&n,&m); rep(i,1,n){ scanf("%d",&w[i]); } clr(f,-0x3f); f[1][0][0]=f[1][1][1]=0; rep(i,2,n){ rep(j,0,m){ if(j>i) continue; if(j==0) f[i&1][j][0]=0; else { f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]); f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+w[i]); } } } int ans=-inf; ans=max(f[n&1][m][0],f[n&1][m][1]); clr(f,-0x3f); f[1][1][1]=w[1]; rep(i,2,n){ rep(j,0,m){ if(j>i) continue; if(j==0) f[i&1][j][0]=0; else { f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]); f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+w[i]); } } } ans=max(ans,f[n&1][m][1]); printf("%d\n",ans); return 0; } ``` # 后效性处理 > 当遇到状态转换有后效性时,可是尝试用列方程组并通过高斯消元求出当前阶段每一个状态的解 ```c++ /*给定一张 N*M 的棋盘,有一个机器人处于(x,y)位置。 这个机器人可以进行很多轮行动,每次等概论地随机选择停在原地、向左移动一格、向右移动一格或向下移动一格。 当然机器人不能移出棋盘。 求机器人从起点走到最后一行的任意一个位置上,所需行动次数的数学期望值。*/ #include
#define clr(a,b) memset(a,b,sizeof(a)) #define ll long long #define ull unsigned long long #define rep(i,a,b) for(int i=a;i<=b;++i) #define repd(i,a,b) for(int i=a;i>=b;--i) #define dbg(a,i,n) printf("c",a," \n"[i==n]) using namespace std; const int inf=0x3f3f3f3f; const ll linf=(1ll<<62)-1; const int N=1e3+7; const int M=4e5+7; const int mod=1e9+7; template inline void read(T &x){ char c;int sign=1;x=0; do{c=getchar();if(c=='-')sign=-1;}while(c<'0'||c>'9'); do{x=x*10+c-'0';c=getchar();}while(c>='0'&&c<='9'); x*=sign; } double a[N][N],f[N][N]; int n,m,x,y; void gauss(){ for(int i=1;i<=m;i++){ //主元化1 double r=a[i][i]; a[i][i]/=r,a[i][i+1]/=r; if(i >n>>m>>x>>y; if(m==1){ printf(".4lf\n",2.0*(n-x)); return 0; } for(int i=n-1;i>=x;i--){ a[1][1]=2.0/3, a[1][2]=-1.0/3, a[1][m+1]=f[i+1][1]/3+1; a[m][m-1]=-1.0/3, a[m][m]=2.0/3, a[m][m+1]=f[i+1][m]/3+1; for(int j=2;j