一点点决策单调性

艹了天天都被不知道的知识点教育,知道的又做不出来。 :lei:

在动态规划中,决策单调性指对于$j < j'$,$f(j')$的最佳转移$f(i')$位置$i'$一定不小于$f(j)$的$i$。

假设

$$f(r)=\min{f(k-1)+w(k,r)}$$

为了证明这一点,首先假设

$$f(r)=f(k-1)+w(k,r)$$

此时我们有$\forall i < k$,

$$f(k-1)+w(k,r) < f(i-1)+w(i,r)$$

此后,将r后挪1。我们必须要证明$\forall i < k$,

$$f(k-1)+w(k,r+1) < f(i-1)+w(i,r+1)$$

也就是证明对于两边的两个增量$\Delta$有

$$w(k,r+1) - w(k,r) < w(i,r+1) - w(i,r)$$

使用分治

一旦有决策单调性了,我们就可以通过优于$n^2$的总复杂度完成规划。

例如使用分治。当我们找到区间$[ l, r ]$的中点$m$的决策点$p$时,那么$[l, m-1]$区间只能从不晚于$p$转移,$[m+1, r]$只能从不早于$p$转移。

l和r是当前要决策的区间,L和R是转移位置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void solve(int l,int r,int L,int R) {
    if(l>r)return;
    int mid=(l+r)/2;
    int p=0;
    for(int i=L;i<=min(mid,R);i++){
        //这里的下标写法要和方程配套
        if(f[i-1]+w[i,mid]<f[mid]){
            f[mid]=f[i-1]+w[i,mid];
            p=i;
        }
    }
    solve(l,mid-1,L,p);
    solve(mid+1,r,p,R);
}

【CF868F】yts1999 Doing Minimization

You are given an array of n integers a1… an. The cost of a subsegment is the number of unordered pairs of distinct indices within the subsegment that contain equal elements. Split the given array into k non-intersecting non-empty subsegments so that the sum of their costs is minimum possible. Each element should be present in exactly one subsegment.

分析

设$f(i,j)$表示前j个分i段。

这个题目就满足决策单调。一个更长的区间+1之后只有可能比短的区间增加更多的代价。

接下来的问题是怎么算这道题里的$w(i,j)$。$n^2$显然不行。看到有人整了个类似莫队的东西,不过我还没太弄明白这个复杂度是怎么算的,看起来是和分治的内层循环一致所以就对复杂度没有更多的贡献了。

一共进行k轮分治,$O(kn\log n)$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using ll=long long;
const int XN=100010;

ll f[30][XN];
ll wage;
int cl,cr;
int num[XN];
ll cnt[XN];
void jump(int l,int r){
    while(cl<l){
        cnt[num[cl]]--;
        wage-=cnt[num[cl]];
        cl++;
    }
    while(l<cl){
        cl--;
        wage+=cnt[num[cl]];
        cnt[num[cl]]++;
    }
    while(r<cr){
        cnt[num[cr]]--;
        wage-=cnt[num[cr]];
        cr--;
    }
    while(cr<r){
        cr++;
        wage+=cnt[num[cr]];
        cnt[num[cr]]++;
    }
}
void solve(int l,int r,int L,int R,int k) {
    if(l>r)return;
    int mid=(l+r)/2;
    int p=0;
    for(int i=L;i<=min(mid,R);i++){
        jump(i,mid);
        if(f[k-1][i-1]+wage<f[k][mid]){
            f[k][mid]=f[k-1][i-1]+wage;
            p=i;
        }
    }

    solve(l,mid-1,L,p,k);
    solve(mid+1,r,p,R,k);
}
int main(){
    ios::sync_with_stdio(false);

    int n,kk;cin>>n>>kk;
    for(int i=1;i<=n;i++)cin>>num[i];
    memset(f,0x3f,sizeof(f));
    for(auto & i : f)i[0]=0;
    for(int i=1;i<=n;i++){
        wage+=cnt[num[i]];
        cnt[num[i]]++;
        f[1][i]=wage;
    }
//    for(int i=1;i<=n;i++){
//        cout<<f[1][i]<<&#039; &#039;;
//    }
//    cout<<endl;
    wage=0;cl=1;cr=0;
    memset(cnt,0,sizeof(cnt));

    for(int i=2;i<=kk;i++){
        solve(1,n,1,n,i);
    }
    cout<<f[kk][n]<<endl;

}