①动态规划初步
紫书上动态规划初步由记忆化搜索引出——当某一阶段的状态需要子状态推出时,递归的求解子状态,并把已经得出的状态记忆化,降低复杂度。
1)记忆化搜索
最经典的是数字三角形,之后引出了在有向无环图上的动态规划问题。在例题巴比伦塔里,建一个每个长方体都有一条边指向一个长宽严格小于它的长方体的图,边权为高,之后开始用记忆化搜索的方式找这个图的最长路,得出ans。
再比如[SHOI2002]滑雪,其实这个不用建图,因为每个点的四个相邻点中,只要高度小于此点就有一条边将它们相连,只需要枚举判断就行。
2)线性dp
事实上每个记忆化搜索都可以转化成线性的dp,只是可能麻烦一点。
现在我的感觉是,要转化成线性dp,首先是要排序。这个状态转移关系用离散数学的术语来说就是每个状态转移的关系要是一种全序关系,然后根据一种优先度来排出一个序。在排好序后dp(i)就一定可以只由之前的i-1个状态得来。
例如[SHOI2002]滑雪也可以有非递归的动态规划做法,读入n*n个点,依据每个点的高度排序,dp(i)表示以第i个点为起点的最长滑坡,前i-1个点的高度都小于等于这个点(可能需要手动剔除等于的点)后面的点也都大于第i个点,所以这样每次都可以确定dp(i)。
由于要排序,显然不如记忆化搜索简单,但是不用递归,可能易读性更强?
巴比伦塔当然也可以用线性dp,只不过要根据长宽的严格小于来对长方体排序。
再比如一些题目会给你规定一个序,这个规定可能是隐含的,也可能是明说了的。
在A Monument For Heroes中,题目规定了序是出现的顺序。
在紫书例题A Spy in the Metro中,隐含的序是时间,设dp(i,j)为时间i在车站j到车站n还需等待的时间,则dp(i,j)一定只由大于i的时间i',在车站j'的状态dp(i',j')推出。所以状态转移中i从大往小枚举
for(int i=T-1;i>=0;i--){ for(int j=1;j<=n;j++){ dp[i][j]=dp[i+1][j]+1; f(i+t[j]<=T&&havetrain[0][j][i]&&j<n){ dp[i][j]=min(dp[i][j],dp[i+t[j]][j+1]); } if(i+t[j-1]<=T&&havetrain[1][j][i]&&j>1){ dp[i][j]=min(dp[i][j],dp[i+t[j-1]][j-1]); } } }
②Codeforces惨遭毒手
这周的教育场被暴打,似乎教育场的C题很多都是数学题。这题自己有一点思路但还是很繁琐,最后也是GG了。看了standing 1的代码真是膜拜了一定要贴一下。
#include <bits/stdc++.h> using namespace std; int f[100000]; long long solve(long long p, int n) { return f[n - 1] * (p / n) + f[p % n]; } int main() { int t; scanf("%d", &t); while(t --) { int a, b, q; scanf("%d %d %d", &a, &b, &q); int n = a * b; for (int i = 1; i < n; i ++) { f[i] = f[i - 1]; if(i % a % b != i % b % a) f[i] ++; } while(q --) { long long l, r; scanf("%lld %lld", &l, &r); printf("%lld ", solve(r, n) - solve(l - 1, n)); } puts(""); } return 0; }