VMware OA 中很难的一题。先从简单的问题开始考虑。
C. Sequence http://codeforces.com/problemset/problem/13/C
lemma:如果操作的次数最少,那么最终得到的不降的数列,必然是由原始数列中的数组成的,具体的证明可以使用反证法。由此我们可以通过限定整个数组递增的上限来dp。
本题的核心是二维dp,第一维i很常规,难点是第二维。由于数组元素较大,我们离散化表示这个cap。我们copy一份数组,进行排序称为b,用b[j]来限定数组递增的上限。
dp[i][j] 使得前i个元素递增,且最大元素(a[i-1]) <= b[j] 的最小cost
dp[i][j] = min { dp[i-1][j]+|a[i-1]-b[j]|, dp[i][j-1] }
base: dp[0][j]=0
result: dp[n][n-1]
用前i个元素的好处就是初始化dp[0][j]为0后,dp[1][j]就可以合并入for循环中。
递推公式比较tricky。如图所示,在更新 dp[i][j] 的时候,有以下可能:
1. a[i-1] >= b[j],这种情况只能把 a[i-1] 减小到 b[j]。
2. a[i-1] < b[j],因为b有序,b[j-1]<b[j],而a[i-1]和b[j-1]都是数组里的元素,推出 a[i-1] <= b[j-1]。符合 dp[i][j-1] 的定义!
观察递推公式,发现可以用滚动数组优化,使得空间复杂度 O(n)
int csequence(vector<int> &a){ vector<int> b=a; sort(b.begin(),b.end()); // dp[i][j] 使得前i个元素递增,且最大元素(a[i-1]) <= b[j] 的最小cost // dp[i][j] = min { dp[i-1][j]+|a[i-1]-b[j]|, dp[i][j-1] } // base: dp[0][j]=0 // result: dp[n][n-1] int n=a.size(); vector<int> dp(n,0); for (int i=1;i<=n;++i){ for (int j=0;j<n;++j){ dp[j] = dp[j]+abs(a[i-1]-b[j]); if (j>0) dp[j] = min(dp[j],dp[j-1]); } } return dp[n-1]; } int main() { //vector<int> a({3,2,-1,2,11}); vector<int> a{2,1,1,1,1}; cout << csequence(a); return 0; }
时间复杂度 O(n^2)
由于本题既可以是递增也可以是递减,所以reverse以后再求递增取小即可。
int csequence(vector<int> &a){ vector<int> b=a; sort(b.begin(),b.end()); // dp[i][j] 使得前i个元素递增,且最大元素(a[i-1]) <= b[j] 的最小cost // dp[i][j] = min { dp[i-1][j]+|a[i-1]-b[j]|, dp[i][j-1] } // base: dp[0][j]=0 // result: dp[n][n-1] int n=a.size(); vector<int> dp(n,0); for (int i=1;i<=n;++i){ for (int j=0;j<n;++j){ dp[j] = dp[j]+abs(a[i-1]-b[j]); if (j>0) dp[j] = min(dp[j],dp[j-1]); } } return dp[n-1]; } int climbhill(vector<int> &a){ int increase=csequence(a); reverse(a.begin(),a.end()); int decrease=csequence(a); cout << increase << ' ' << decrease << endl; return min(increase,decrease); } int main() { vector<int> a{9,8,7,2,3,3}; cout << climbhill(a); return 0; }
时间复杂度 O(n^2)