CF13C Sequence
偶然看到的这道题,觉得有点意思,就做了。
首先题里说修改后的数列只能出现修改前的数,那么状态之间的转移也就之可能在这些数之间。
令f[i][j]表示第 i 个数改成原序列第 j 小的数时的最小步数。容易得出:f[i][j] = min(f[i - 1][k]) + abs(a[i] - b[j]) (1 <= k <= j),其中a是原序列,b是排好序的序列。这样实现了一个O(n3)的dp。
然而n <= 5000,因此我们必须优化掉一层循环,考虑k那一层:f[i - 1][k]其实就是取前缀f[i - 1][j]中的最小值,令g[i][j]表示第 i 个数改成小于等于第 j 个数时的最小步数,那么g[i][j]可以用f[i][j]动态维护,有转移方程:
f[i][j] = g[i - 1][j] + abs(a[i] - b[j])
g[i][j] = f[i][j] (j = 1)
= min(g[i][j - 1], f[i][j]) (j > 1)
那么答案就是g[n][n]。
另外这道题限制64MB,因此空间上把第一维优化掉。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cctype> 4 using namespace std; 5 #define enter puts("") 6 #define space putchar(' ') 7 #define Mem(a, x) memset(a, x, sizeof(a)) 8 #define rg register 9 typedef long long ll; 10 typedef double db; 11 const int INF = 0x3f3f3f3f; 12 const db eps = 1e-8; 13 const int maxn = 5e3 + 5; 14 inline ll read() 15 { 16 ll ans = 0; 17 char ch = getchar(), las = ' '; 18 while(!isdigit(ch)) las = ch, ch = getchar(); 19 while(isdigit(ch)) ans = ans * 10 + ch - '0', ch = getchar(); 20 if(las == '-') ans = -ans; 21 return ans; 22 } 23 inline void write(ll x) 24 { 25 if(x < 0) putchar('-'), x = -x; 26 if(x >= 10) write(x / 10); 27 putchar(x % 10 + '0'); 28 } 29 30 int a[maxn], b[maxn], n; 31 ll f[maxn], g[maxn]; 32 33 int main() 34 { 35 n = read(); 36 for(int i = 1; i <= n; ++i) a[i] = b[i] = read(); 37 sort(b + 1, b + n + 1); 38 for(int i = 1; i <= n; ++i) 39 for(int j = 1; j <= n; ++j) 40 { 41 f[j] = g[j] + abs(a[i] - b[j]); 42 g[j] = j == 1 ? f[j] : min(g[j - 1], f[j]); 43 } 44 write(g[n]); enter; 45 return 0; 46 }