艹了天天都被不知道的知识点教育,知道的又做不出来。 :lei:
在动态规划中,决策单调性指对于j < j ′ j < j' j < j ′ ,f ( j ′ ) f(j') f ( j ′ ) 的最佳转移f ( i ′ ) f(i') f ( i ′ ) 位置i ′ i' i ′ 一定不小于f ( j ) f(j) f ( j ) 的i i i 。
假设
f ( r ) = min f ( k − 1 ) + w ( k , r ) f(r)=\min{f(k-1)+w(k,r)}
f ( r ) = min f ( k − 1 ) + w ( k , r )
为了证明这一点,首先假设
f ( r ) = f ( k − 1 ) + w ( k , r ) f(r)=f(k-1)+w(k,r)
f ( r ) = f ( k − 1 ) + w ( k , r )
此时我们有∀ i < k \forall i < k ∀ i < k ,
f ( k − 1 ) + w ( k , r ) < f ( i − 1 ) + w ( i , r ) f(k-1)+w(k,r) < f(i-1)+w(i,r)
f ( k − 1 ) + w ( k , r ) < f ( i − 1 ) + w ( i , r )
此后,将r后挪1。我们必须要证明∀ i < k \forall i < k ∀ i < k ,
f ( k − 1 ) + w ( k , r + 1 ) < f ( i − 1 ) + w ( i , r + 1 ) f(k-1)+w(k,r+1) < f(i-1)+w(i,r+1)
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 ) w(k,r+1) - w(k,r) < w(i,r+1) - w(i,r)
w ( k , r + 1 ) − w ( k , r ) < w ( i , r + 1 ) − w ( i , r )
使用分治
一旦有决策单调性了,我们就可以通过优于n 2 n^2 n 2 的总复杂度完成规划。
例如使用分治。当我们找到区间[ l , r ] [ l, r ] [ l , r ] 的中点m m m 的决策点p p p 时,那么[ l , m − 1 ] [l, m-1] [ l , m − 1 ] 区间只能从不晚于p p p 转移,[ m + 1 , r ] [m+1, r] [ m + 1 , r ] 只能从不早于p p 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 ) f(i,j) f ( i , j ) 表示前j个分i段。
这个题目就满足决策单调。一个更长的区间+1之后只有可能比短的区间增加更多的代价。
接下来的问题是怎么算这道题里的w ( i , j ) w(i,j) w ( i , j ) 。n 2 n^2 n 2 显然不行。看到有人整了个类似莫队的东西,不过我还没太弄明白这个复杂度是怎么算的,看起来是和分治的内层循环一致所以就对复杂度没有更多的贡献了。
一共进行k轮分治,O ( k n log n ) O(kn\log n) O ( k n 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; } 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; }