wqs二分中二分条件细节的思考

先举例复习一下,给定正整数数组a[],将数组划分为k份,求k个子段平方和之和的最大值(即 min ⁡ { ∑ i = 1 k s u m i 2 } \min\{\sum^{k}_{i = 1}{sum_i^2}\} min{i=1ksumi2})。

转移方程为 d p [ i ] [ j ] = m a x { d p [ x ] [ j − 1 ] + w ( x + 1 , i ) } dp[i][j] = max\{ dp[x][j - 1] + w(x + 1, i) \} dp[i][j]=max{dp[x][j1]+w(x+1,i)} ,要求 d p [ n ] [ k ] dp[n][k] dp[n][k]。2D1D动态规划时间复杂度为 O ( n 3 ) O(n^3) O(n3),需要优化。考虑 f [ k ] = d p [ n ] [ k ] f[k] = dp[n][k] f[k]=dp[n][k] f f f 是凸函数,构造一条与 ( k , f [ k ] ) (k, f[k]) (k,f[k]) 相切的直线即可求 f [ k ] f[k] f[k], 对该直线的斜率进行二分可下降复杂度至 O ( log ⁡ n ) O(\log{n}) O(logn)。结合该题更形象地说,k越大,最大值越难变大,为凸。我们二分一个代价 v a l val val,每划分一次答案就减去 v a l val val,即 d p [ i ] = m a x { d p [ x ] + w ( x + 1 , i ) − v a l } dp[i] = max\{ dp[x] + w(x + 1, i) - val \} dp[i]=max{dp[x]+w(x+1,i)val},以此表示“划分越多最大值越难增长”。当 v a l val val 过大, f f f k k k 较小处取得最值;反之亦然。对于一个合适的 v a l val val,就可在题目给定的 k k k 值出取得最值,此时就找到了“切线”。

struct DP { int f, k; } dp[N];

bool operator < (DP a, DP b) {
    if (a.f != b.f) return a.f < b.f;
    return a.k < b.k;
}
    
inline DP solve (int val) {
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < i; j++) {
			dp[i] = max(dp[i], { dp[j].f + w(j + 1, i) - val, dp[j].k + 1 });
        }
    }
    return dp[n];
}

int wqs (void) {
	int st, en, md;
    while (st != en) {
		int md = (st + en + 1) >> 1;
        if (solve(md).k > k) st = md;
        else en = md - 1;
    }
    return solve(st).f;
}

需要注意的是函数可能不是严格凸的,可能切线同时经过好几个点。以该例题说明,对于某一 v a l val val ,多个 k k k 都使 d p [ n ] dp[n] dp[n] 有最大值,二分时我们得注意不能把题目要求 k k k 排除。从重载小于符号中我们看出,我们最终返回的 d p [ n ] . k dp[n].k dp[n].k 是满足条件的最大的 k k k ,二分要考虑到这一点。那么如果重载时反过来写呢?如下:

bool operator < (DP a, DP b) {
	if (a.f != b.f) return a.f < b.f;
    return a.k > b.k;
}

int wqs (void) {
    ...
        int md = (st + en) >> 1;
        if (solve(md).k > k) st = md + 1;
        else en = md;
    ...
}

总之总之debug时要想起这一点哦,不过也轮不到我来敲这种题。

你可能感兴趣的:(wqs二分中二分条件细节的思考)