题目:(非常经典的模拟赛题,适合动规入门的OIer)
简要分析:
动态规划,用一维数组 f[i] 表示从位置1 到 位置i 的最优花费 ,由于 f[i ] 以前的最优花费都是确定的,故只需要在 1 to i 中 枚举变量 j 用来分段.即 把 Q[1 , i ]分为了 Q[1 , j ] + Q[ j+1 , i].其中Q[ 1 , j ]已经被算出,而 Q[ j+1 , i ]又能很轻易的由题目所给公式得到,状态转移方程 f[ i ] = min { f[j-1] + k + ( i-j+1 )*(max[i,j]-min[i,j]) | i-j>m ,j >=1,j<=i }.
代码实现:
1.朴素动规,即老老实实的 把 max[i,j] 和 min[i,j]给预处理出来,可能会爆内存,不能得全分,但建议阅读:
namespace last { //笼统预处理版本 << ; int n, k, m; long long a[(int)4e4]; ][(], fmin[(][(]; long long f[(int)4e4]; int main() { cin >> n >> m >> k; ; i <= n; i++) cin >> a[i], f[i] = i * k; ; i <= n; i++) { //预处理出区间极值 fmax[i][i] = fmin[i][i] = a[i]; ; j <= n; j++) { fmax[i][j] = max(fmax[i][j - ], a[j]); fmin[i][j] = min(fmin[i][j - ], a[j]); } } f[] = k; //初始化 ; i <= n; i++) { f[i]=1e+; , ); j <= i; j++) //简单的状态转移 f[i] = min(f[i], f[j - ] + k + (i - j + ) * (fmax[j][i] - fmin[j][i])); } cout << f[n] << endl; //system("pause"); ; } }
2.在状态转移时求出当前 max,min,为了使max,min适用于 Q[ j , i ],因此枚举 j 时采用倒序:
namespace newn { //更巧妙的方法 int a[maxn], n, m, k; lnt f[maxn]; int main() { scanf("%d%d%d", &n, &m, &k); ; i <= n; i++) scanf("%d", &a[i]); ; i <= n; i++) { f[i] = 1e18; , mn = 1e9; //可以边走边算最值,不用预处理,但需要逆序 ; j--) { if (a[j] < mn) mn = a[j]; if (a[j] > mx) mx = a[j]; f[i] = min(f[i], f[j - ] + k + 1ll * (mx - mn) * (i - j + )); } } printf("%lld\n", f[n]); //system("pause"); } }
3.最后给出主函数(其实没必要的)
int main() { /**/ freopen("toy.in", "r", stdin); freopen("toy.out", "w", stdout); /**/ last::main(); newn::main(); ; }
4.总结一下,动规也是非常有技巧性的