【学习笔记】各类基于决策单调性的dp优化
文章目录
- 对于决策单调性的一般解释
- 关于决策单调性的证明
- 四边形不等式
- 一维dp
- 区间dp
- 一种二维dp
- 一些满足四边形不等式的函数类
- 与图形相结合
- 决策单调性的常见优化手段
- 二分队列
- 二分栈
- 分治
- 类莫队做法
- SMAWK
- WQS二分+
- WQS多解情况
- 满足四边形不等式的序列划分问题的答案凸性以及WQS二分的方案构造
- WQS外层二分时的边界
- Tips:
- 斜率优化
- 一些特殊情况
本文与 [学习笔记]斜率优化dp 总结内容相互关联,建议放在一起阅读(不这样也没事,我只是打个广告:)
对于决策单调性的一般解释
众所周知,dp中会有转移,每一个状态可能会有若干个状态转移而来。现在我们考虑一类较为特殊的dp,最优化dp,在其中每一个点只能从一个最优状态转移而来。在此基础上,如果随着dp顺序的推进,每一个点的最优转移点也是单调移动的,我们就称其具有决策单调性.
比如说,对于一个常见一维 d p : d p i = m i n / m a x { d p j + w ( j , i ) } dp:dp_i=min/max\{dp_j+w(j,i)\} dp:dpi=min/max{dpj+w(j,i)},显然每一个点的dp值只能从 { j ∣ j < i } \{j|j<i\} {j∣j<i}的 d p j dp_j dpj转移而来,我们记每一个点 i i i的最优决策转移点为 p i p_i pi.如果我们从1-n遍历i, p i p_i pi也随之单调变化,那么这个dp就具有决策单调性了.大部分决策单调性体现在决策点单调递增,但是也有决策点单调递减的情况(都是建立在枚举的点是从小到大的情况下)
关于决策单调性的证明
显然我们可以打表。只要暴力写一个dp,然后毛估估看一看转移点是否单调即可。当然还有其它更加稳妥的方式
该部分讲解如无特殊声明,都是建立在求最小化问题的基础上,如果要求最大化的话,要把对应不等号取反
四边形不等式
一维dp
对应方程形式 F F F : d p i = m i n j < i { d p j + w ( j , i ) } dp_i=min_{j<i}\{dp_j+w(j,i)\} dpi=minj<i{dpj+w(j,i)}
我们考虑 p 1 ≤ p 2 ≤ p 3 ≤ p 4 p_1\leq p_2\leq p_3\leq p_4 p1≤p2≤p3≤p4
则最小化情况下的四边形不等式表示为: w ( p 1 , p 3 ) + w ( p 2 , p 4 ) ≤ w ( p 1 , p 4 ) + w ( p 2 , p 3 ) w(p_1,p_3)+w(p_2,p_4)\leq w(p_1,p_4)+w(p_2,p_3) w(p1,p3)+w(p2,p4)≤w(p1,p4)+w(p2,p3)
特别的,如果等号永远成立,称为四边形恒等式
一般记为:交叉 ≤ \leq ≤包含
定理1:如果对于dp式F,其w满足四边形不等式,则F满足决策单调性
-
P r o o f : Proof: Proof:
反证法。记 d p i dp_i dpi的最优决策点为 p i p_i pi,假设 y < x y<x y<x且 p x < p y p_x<p_y px<py.根据 F F F的定义有: d p x = d p p x + w ( p x , x ) ≤ d p p y + w ( p y , x ) ( 1 ) dp_x=dp_{p_x}+w(p_x,x)\leq dp_{p_y}+w(p_y,x)(1) dpx=dppx+w(px,x)≤dppy+w(py,x)(1)
不难发现 p x < p y < y < x p_x<p_y<y<x px<py<y<x,故由四边形不等式: w ( p x , y ) + w ( p y , x ) ≤ w ( p x , x ) + w ( p y , y ) ( 2 ) w(p_x,y)+w(p_y,x)\leq w(p_x,x)+w(p_y,y)(2) w(px,y)+w(py,x)≤w(px,x)+w(py,y)(2)
1 , 2 1,2 1,2两式相加得到: d p p x + w ( p x , y ) ≤ d p p y + w ( p y , y ) dp_{p_x}+w(p_x,y)\leq dp_{p_y}+w(p_y,y) dppx+w(px,y)≤dppy+w(py,y)
则对于 y y y来说, p x p_x px是一个比 p y p_y py更优的决策点,与条件矛盾。
故 y < x y<x y<x意味着 p y ≤ p x p_y\leq p_x py≤px,单调性得证
区间dp
一般来说高维dp的决策单调性体现在某一个维度,相对一维dp来说也会更加难发现,但是一种区间dp具有较好的性质
对应方程形式 F F F : d p i , j = m i n k = i j − 1 { d p i , k + d p k + 1 , j + w ( i , j ) } :dp_{i,j}=min_{k=i}^{j-1}\{dp_{i,k}+dp_{k+1,j}+w(i,j)\} :dpi,j=mink=ij−1{dpi,k+dpk+1,j+w(i,j)}
这里先介绍一个跟四边形不等式非常像的区间包含不等式:考虑 p 1 ≤ p 2 ≤ p 3 ≤ p 4 p_1\leq p_2\leq p_3\leq p_4 p1≤p2≤p3≤p4,则 w ( p 1 , p 4 ) ≥ w ( p 2 , p 3 ) w(p_1,p_4)\geq w(p_2,p_3) w(p1,p4)≥w(p2,p3)
引理1:如果对于dp式F,其w同时满足四边形不等式以及区间包含不等式,则F也满足四边形不等式
-
P r o o f : Proof: Proof:
我不会但是有人会
定理2:如果对于dp式F,其满足四边形不等式,记 p i , j 为 d p i , j p_{i,j}为dp_{i,j} pi,j为dpi,j的最优决策点,则 ∀ i < j , p i , j − 1 ≤ p i , j ≤ p i + 1 , j \forall i<j,p_{i,j-1}\leq p_{i,j}\leq p_{i+1,j} ∀i<j,pi,j−1≤pi,j≤pi+1,j
-
P r o o f : Proof: Proof:
不会+1但是有人会
基于此,我们对于 F F F就能给出一个复杂度为 O ( n 2 ) O(n^2) O(n2)的优化。
如果在计算 f i , j f_{i,j} fi,j的同时记录 p i , j p_{i,j} pi,j,则对决策点 K K K的枚举量为 ∑ 1 ≤ i < j ≤ n p i + 1 , j − p i , j − 1 = ∑ i = 1 n p i , n − p 1 , i ≤ n 2 \sum_{1\leq i<j\leq n}p_{i+1,j}-p_{i,j-1}= \sum_{i=1}^{n}p_{i,n}-p_{1,i}\leq n^2 ∑1≤i<j≤npi+1,j−pi,j−1=∑i=1npi,n−p1,i≤n2
例题:
HDU Monkey Party
HDU Tree Construction
大致来说就是看到对应形式,证一下四边形不等式,包含不等式,然后套板子即可
这里给个Monkey Party的代码(幼年码风,不喜勿喷)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2020;
ll n;
ll mas[N];
ll dp[N][N];
ll s[N][N];
ll sum[N];
ll w(ll l,ll r)
{return sum[r]-sum[l-1];
}
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);while(cin>>n){for(int i=1;i<=n;++i) cin>>mas[i];for(int i=1+n;i<=n+n;++i) mas[i]=mas[i-n];for(int i=1;i<=n+n;++i) sum[i]=sum[i-1]+mas[i];memset(dp,0x3f,sizeof dp);for(int i=0;i<=n+n;++i) dp[i][i]=0,s[i][i]=i;for(int len=2;len<=n;len++){for(int i=1;i<=2*n-len+1;i++){int j=i+len-1;for(int k=s[i][j-1];k<=s[i+1][j];k++){if(dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]){dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];s[i][j]=k;}}}}ll ans=1e17;for(int l=1;l<=n;++l){ans=min(ans,dp[l][l+n-1]);}cout<<ans<<endl;}return 0;
}
一种二维dp
这一块存疑
也是一种非常常见的dp式 f f f : d p i , j = m i n k < i { d p k , j − 1 + w ( k , i ) } :dp_{i,j}=min_{k<i}\{dp_{k,j-1}+w(k,i)\} :dpi,j=mink<i{dpk,j−1+w(k,i)}
按理说它跟上一块的区间dp是不同类型的,但是仿佛只要 w w w满足引理1, F F F也有同样的结论。我没看到过严格证明,OIWIKI上也没找到对应介绍,但是大家好像都默认这是正确的,而且我目前也没找到反例…并不是很懂
反正用定理2来优化该dp的话,时间复杂度是 O ( n 2 ) O(n^2) O(n2)的
例题:
HDU Division
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=10005;
ll t,n,m;
ll dp[N][5010];
ll mas[N];
ll f(ll l,ll r)
{return (mas[r]-mas[l])*(mas[r]-mas[l]);
}
ll d[N][5010];
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>t;for(int s=1;s<=t;++s){cin>>n>>m;for(int i=1;i<=n;++i) cin>>mas[i];sort(mas+1,mas+1+n);memset(dp,0x3f,sizeof dp);dp[0][0]=0;for(int j=1;j<=m;++j){d[n+1][j]=n;for(int i=n;i;--i){ll tmp=0x3f3f3f3f;ll p;for(int k=d[i][j-1];k<=d[i+1][j];++k){if(tmp>dp[k][j-1]+f(k+1,i)){tmp=dp[k][j-1]+f(k+1,i);p=k;}}dp[i][j]=tmp;d[i][j]=p;}}cout<<"Case "<<s<<": "<<dp[n][m]<<endl;}return 0;
}
一些满足四边形不等式的函数类
- 若 w 1 ( i , j ) , w 2 ( i , j ) w_{1(i,j)},w_{2(i,j)} w1(i,j),w2(i,j)均满足四边形不等式(或区间包含不等式),则 ∀ c 1 , c 2 ≥ 0 \forall c_1,c_2\geq 0 ∀c1,c2≥0, c 1 w 1 ( i , j ) + c 2 w 2 ( i , j ) c_1w_{1(i,j)}+c_2w_{2(i,j)} c1w1(i,j)+c2w2(i,j)也对应地满足四边形不等式(或区间包含不等式)
- 若存在函数 f , g f,g f,g,使得 w l , r = f r − g l w_{l,r}=f_r-g_l wl,r=fr−gl,则 w w w满足四边形恒等式(等号永远成立)。当 f , g f,g f,g单调增的时候, w w w还满足区间包含不等式
- 设 h x h_x hx是一个单调增的下凸函数,若 w w w满足四边形不等式以及区间包含不等式,则 h ( w ( x ) ) h(w(x)) h(w(x))也满足四边形不等式以及区间包含不等式
- 设 h x h_x hx是一个下凸函数,若 w w w满足四边形恒等式以及区间包含不等式,则 h ( w ( x ) ) h(w(x)) h(w(x))满足四边形不等式
具体证明详见OIWIKI
这里以NOI 2009 诗人小G的dp递推式来试着用这些结论证一下决策单调性
d p i = m i n 0 ≤ j < i { d p j + ∣ s i − s j − 1 − L ∣ P } dp_i=min_{0\leq j<i}\{dp_j+|s_i-s_j-1-L|^P\} dpi=min0≤j<i{dpj+∣si−sj−1−L∣P},其中 s i = i + ∑ k = 1 i a k s_i=i+\sum_{k=1}^{i}a_k si=i+∑k=1iak, a k > 0 a_k>0 ak>0为常值
这里 w j , i = ∣ s i − s j − 1 − L ∣ P w_{j,i}=|s_i-s_j-1-L|^P wj,i=∣si−sj−1−L∣P,我们不妨记 m j , i = s i − s j − 1 − L m_{j,i}=s_i-s_j-1-L mj,i=si−sj−1−L,则 w j , i = ∣ m j , i ∣ P w_{j,i}=|m_{j,i}|^P wj,i=∣mj,i∣P
我们考虑利用第2条结论:记 f i = s i − 1 − L , g i = s i f_i=s_i-1-L,g_i=s_i fi=si−1−L,gi=si,显然 m j , i = f i − g j m_{j,i}=f_i-g_j mj,i=fi−gj,故 m j , i m_{j,i} mj,i满足四边形不等式,又 f i , g i f_i,g_i fi,gi显然单增,故 m j , i m_{j,i} mj,i满足区间包含不等式
又函数 ∣ x ∣ P |x|^P ∣x∣P显然是一个下凸函数,故由第4条结论可知, w j , i = m j , i w_{j,i}=m_{j,i} wj,i=mj,i也满足四边形不等式。再由定理1,该dp式满足决策单调性。证毕。
可以看到如果式子能记住的话,在面对一些比较复杂的递推式的时候能够较从容地推出决策单调性,如果直接用四边形不等式的定义硬证这题的话,还是需要不少分类讨论和强大的推柿子能力的(啊…只会套公式的屑)
与图形相结合
来自一位大佬的奇妙讨论,从另一个视角帮助我们理解了决策单调性
这里讨论一维最优化dp,也就是 F F F : d p i = m i n j ≤ i { d p j + w ( j , i ) } :dp_i=min_{j\leq i}\{dp_j+w(j,i)\} :dpi=minj≤i{dpj+w(j,i)}
决策单调性意味着决策点是单调的,换句话说,每一个点能够作为最优决策点的范围是一段连续区间
在绿色区间内的点都是被 d p j 1 dp_{j_1} dpj1更新,在黄色区间内的点都是被 d p j 2 dp_{j_2} dpj2更新,不会出现黄色区间内有某个点是被 d p j 1 dp_{j_1} dpj1更新的情况,否则就违背了决策单调性的定义
从几何视角来看这一事实。我们考虑 g j ( i ) g_j(i) gj(i),表示 d p j + w ( j , i ) dp_j+w(j,i) dpj+w(j,i),则 d p i dp_i dpi可以重新表示为 d p i = m i n j ≤ i { g j ( i ) } dp_i=min_{j\leq i}\{g_j(i)\} dpi=minj≤i{gj(i)}。我们可以将 g j ( i ) g_j(i) gj(i)看成 j j j为常值, i i i为自变量的一个函数,那么 d p i dp_i dpi本质上就是在所有函数 g j ( i ) g_j(i) gj(i)里取最小值来转移
还是以上图为例,绿色区间内的点对应函数 g j 1 ( i ) g_{j_1}(i) gj1(i),黄色区间内对应函数 g j 2 ( i ) g_{j_2}(i) gj2(i),我们可以得到一个结论:两个函数只能有一个交点,否则就会出现前一段是 g j 1 ( i ) g_{j_1}(i) gj1(i)更小,中间是 g j 2 ( i ) g_{j_2}(i) gj2(i),后面又变成 g j 1 ( i ) g_{j_1}(i) gj1(i)更小的局面,那就违背了决策单调性的定义。反过来,如果只有一个交点的话,这两段区间就满足决策单调性
整体来看,我们可以得到:如果所有 g j ( i ) g_j(i) gj(i)两两之间都至多只有一个交点的话, F F F是满足决策单调性的。个人感觉该命题的逆命题并不成立,毕竟实际在转移的时候只用考虑最小值的变化,值较大的函数之间是如何纠缠的我们并不关系。所以该结论应该只适用于证明决策单调性,而不能证否决策单调性
接下来我们考虑,如果 g j ( i ) g_j(i) gj(i)之间至多只有一个交点,需要满足什么性质
该大佬给出的两个条件是:
- g j ( i ) g_j(i) gj(i)之间可以通过平移相互变换
- g j ( i ) g_j(i) gj(i)的导函数在定义域内单调增/减(凸性唯一)
以下分别是不满足对应条件的反例
这两个条件不一定充分,只是感觉上可能也够了
然后大佬给出的一些总结:
-
如果导函数递增,求最大值,或导函数递减,求最小值 用单调栈
-
如果导函数递增,求最小值,或导函数递减,求最大值 用单调队列
具体怎么用单调栈,单调队列,我会在后面有所涉及
决策单调性的常见优化手段
二分队列
这应该算是最常见的一种利用决策单调性来优化dp的手段了。它只能用于决策点单调增的情况。
考虑之前的一个结论:每一个决策点的更新范围是一段连续区间。这意味着相邻两个决策点的更新范围之间存在着一个分界线 K K K,当 i < = k i<=k i<=k的时候,由前一个点 j 1 j_1 j1更新,之后就由后一个点 j 2 j_2 j2更新,并且 j 1 j_1 j1就再也没有机会了。那么我们就可以维护一个单调队列来实现每次 O ( l o g n ) O(logn) O(logn)的更新
具体来说,我们的单调队列中的每一个元素是一个三元组 { P , l p , r p } \{P,l_p,r_p\} {P,lp,rp},分别表示当前的决策点 P P P,它能更新区间的左界 l p l_p lp,它能更新区间的右界 r p r_p rp。那么当我们尝试更新 d p i dp_i dpi的时候,
-
弹出队头。因为队列里的点的对应决策区间是单调的,所以我们可以直接将 r p < i r_p<i rp<i的队头三元组不断弹出。
-
用队头的 P P P更新 d p i dp_i dpi
-
将三元组 { i , l i , r i } \{i,l_i,r_i\} {i,li,ri}放入队列尾部。那么怎么求 l i , r i l_i,r_i li,ri?在求 l i l_i li之前,我们要意识到一个事实:我们所求的 l p , r p l_p,r_p lp,rp都是建立在已经遍历到的点的信息上而建立的,换句话说,随着后面新的点 i i i加入,它的决策区间有可能会完全包含某一个点 P P P的决策区间。这一点很好理解:原本 l i , r i l_i,r_i li,ri就是由 i i i更新的,但是因为我们还没有遍历到 i i i,所以这段区间就会先被其它点占据。所以我们在求 l i l_i li之前,要先将队尾那些完全不会比 i i i优的点踢出。具体如何判断?我们只要看一下在 l p l_p lp处 i i i是否比 P P P更优即可。如果是,显然 P P P就再也没有机会比 i i i更优了,因为 p < i p<i p<i。
该操作之后 i i i就不能将队尾的 P P P的区间完全覆盖了,我们就可以利用单调性二分一下两者的决策区间的边界,作为 l i l_i li.至于 r i r_i ri,我们直接设成 n n n即可。毕竟此时后面的点还没加入,我们只能用 i i i来更新它们。
贴个模板
//该模板适用于最小化问题,若求最大化,自行将对应不等号取反即可
//cal(j,i):g_j(i)ll que[N];//单调队列hd=1,tl=0;que[++tl]=0;ls[0]=1,rs[0]=n;for(int i=1;i<=n;++i){while(hd<tl&&rs[que[hd]]<i) hd++;//弹出无用点dp[i]=cal(que[hd],i);pre[i]=que[hd];while(hd<tl&&cal(i,ls[que[tl]])<=cal(que[tl],ls[que[tl]])) tl--;//弹出无用点ll l=ls[que[tl]],r=n+1;while(l<=r){ll mid=(l+r)>>1;if(cal(i,mid)<=cal(que[tl],mid)) r=mid-1;//二分查找i与que[tl]的转移分界点,也就是最小的满足i优于que[tl]的点else l=mid+1;}p_ans=r+1;if(p_ans>n) continue;//i并没有用rs[que[tl]]=p_ans-1;que[++tl]=i;//插入队列ls[i]=p_ans,rs[i]=n;}
Tips:二分决策区间边界的时候,右界设置为n+1,因为i有可能完全不如P优,要防一手
如果 w ( j , i ) w(j,i) w(j,i)我们能够以 O ( 1 ) O(1) O(1)的复杂度求出的话,该方法的时间复杂度式 O ( n l o g ) O(nlog) O(nlog)的
例题:
NOI 2009 诗人小G
dp式并不难列出,关于其决策单调性我们已经在上一块里证明过了,所以这里直接用二分队列即可
数据范围有点大,记得开long double
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define IL inline
#define endl '\n'
const ll N=100010;
const ll mod=998244353;
const ll inf=1e18;
inline int read()
{int X=0; bool flag=1; char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}if(flag) return X;return ~(X-1);
}
int n,L,P;
char s[N][50];
ld mas[N];
int ls[N],rs[N],pre[N];//记录最优决策点
int que[N];
int hd,tl,p_ans;
ld dp[N];
ld ksm(ld x,ll y)
{ld ans=1;while(y){if(y&1) ans=ans*x;x=x*x;y>>=1;}return ans;
}
ld cal(ll k,ll x)
{return dp[k]+ksm(abs(mas[x]-mas[k]-1-L),P);
}
void solve()
{n=read();L=read();P=read();for(int i=1;i<=n;++i) scanf("%s",s[i]);// for(int i=1;i<=n;++i) cin>>s[i];for(int i=1;i<=n;++i){mas[i]=mas[i-1]+strlen(s[i])+1;}hd=1,tl=0;que[++tl]=0;ls[0]=1,rs[0]=n;for(int i=1;i<=n;++i){while(hd<tl&&rs[que[hd]]<i) hd++;dp[i]=cal(que[hd],i);pre[i]=que[hd];while(hd<tl&&cal(i,ls[que[tl]])<=cal(que[tl],ls[que[tl]])) tl--;ll l=ls[que[tl]],r=n+1;while(l<=r){ll mid=(l+r)>>1;if(cal(i,mid)<=cal(que[tl],mid)) r=mid-1;//二分查找i与que[tl]的转移分界点,也就是最小的满足i优于que[tl]的点else l=mid+1;}p_ans=r+1;if(p_ans>n) continue;//i并没有用rs[que[tl]]=p_ans-1;que[++tl]=i;//插入队列ls[i]=p_ans,rs[i]=n;}///if(dp[n]>inf){puts("Too hard to arrange");}else{printf("%lld\n",(ll)dp[n]);vector<string> ans;ll pos=n;while(1){ll x=pre[pos]+1;string ss="";for(int i=x;i<pos;++i) ss+=s[i],ss+=" ";ss+=s[pos];ans.push_back(ss);pos=pre[pos];if(pos==0) break;}reverse(ans.begin(),ans.end());for(auto i:ans) cout<<i<<endl;}puts("--------------------");
}
int main()
{// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);ll t;t=read();while(t--)solve();return 0;
}
二分栈
一般用于最优决策点是不增的情况。在原本的二分队列的做法中,我们从队列的首部取出最优决策点,从队列的尾部放入决策点。那么如果决策点是不增的,意味着我们需要从队列的尾部取最优决策点。这样取和放的操作都是在队列的尾部,我们只要用一个栈来实现就好了。同样的,相邻的两个决策点更新的区间会有一个边界,我们不妨记为 k l , r k_{l,r} kl,r,当尝试加入 i i i的时候,记队列倒数第一个元素为 p 1 p_1 p1,倒数第二个元素为 p 2 p_2 p2,那么如果 k p 1 , p 2 ≤ k p 2 , i k_{p_1,p_2}\leq k_{p_2,i} kp1,p2≤kp2,i,那么 p 2 p_2 p2就可以踢掉了,因为它不管在什么时候都不会是 i , p 1 , p 2 i,p_1,p_2 i,p1,p2中的最优解。然后将 i i i加入队列之后再按照 k p 1 , p 2 k_{p_1,p_2} kp1,p2是否 ≤ i \leq i ≤i来踢即可
JSOI2001 柠檬
思路:
首先不难发现最优选择下每一段的首尾的颜色就是这段的 s 0 s_0 s0,否则我们就可以将首尾的没用的点分出去自称一段,贡献一定更优
所以我们可以按照不同的颜色来分开处理
d p i = m a x c o l j = c o l i , j ≤ i { d p j − 1 + w ( j , i ) } dp_i=max_{col_j=col_i,j\leq i}\{dp_{j-1}+w(j,i)\} dpi=maxcolj=coli,j≤i{dpj−1+w(j,i)},其中 w ( j , i ) = ( s u m i − s u m j + 1 ) 2 w(j,i)=(sum_i-sum_j+1)^2 w(j,i)=(sumi−sumj+1)2
这里我们画图就能发现决策点是单调不增的(按照上文与图形结合的方法),所以可以使用二分栈。但是这题的决策单调性是在相同颜色的点之间才存在的,所以我们要分开来做二分栈
然后每一个点不会从0转移,所以一开始也不用把0放进去
code
#include <bits/stdc++.h>
#define pii pair<int,int>
#define il inline
#define ll long long
using namespace std;
#define bc(idx) vt[idx].size()-1
#define bd(idx) vt[idx].size()-2
#define g(a,b) vt[a][b]
const int N=200000;
const ll inf=1e18;
ll n;
ll mas[N];
vector<ll> vt[N];
ll dp[N];
ll ls[N],rs[N];
map<ll,ll> mp;
ll sum[N];
ll gt(ll k,ll num)
{return dp[k-1]+mas[k]*num*num;
}
ll gtf(ll a,ll b)
{ll l=1,r=n;while(l<=r){ll mid=l+r>>1;if(gt(a,mid-sum[a]+1)>=gt(b,mid-sum[b]+1)) r=mid-1;else l=mid+1;}return r+1;
}
void solve()
{cin>>n;for(int i=1;i<=n;++i){cin>>mas[i];mp[mas[i]]++;sum[i]=mp[mas[i]];}for(int i=1;i<=n;++i){ll idx=mas[i];while(vt[idx].size()>=2&& gtf(g(idx,bd(idx)),g(idx,bc(idx)))<=gtf(g(idx,bc(idx)),i)) vt[idx].pop_back();vt[idx].push_back(i);while(vt[idx].size()>=2&& gtf(g(idx,bd(idx)),g(idx,bc(idx)))<=sum[i]) vt[idx].pop_back();dp[i]=gt(g(idx,bc(idx)),sum[i]-sum[g(idx,bc(idx))]+1);}cout<<dp[n]<<endl;
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);solve();return 0;
}
分治
分治的方法主要用于同一层之间的点相互之间线性无关的情况。
比如 F 1 F_1 F1 : d p i , j = m i n { d p i − 1 , k + w ( k , i ) } :dp_{i,j}=min\{dp_{i-1,k}+w(k,i)\} :dpi,j=min{dpi−1,k+w(k,i)}. F 2 F_2 F2 : d p i = m i n { a k + w ( k , i ) } :dp_i=min\{a_k+w(k,i)\} :dpi=min{ak+w(k,i)}
可以看到同一层之间的点不会相互转移
那么如果同一层的点满足决策单调性的话,也就是意味着决策点是单调的,我们就可以采用分治的策略来优化转移的过程
假设当前需要转移的区间是 [ L , R ] [L,R] [L,R],决策点的选择区间是 [ p l , p r ] [p_l,p_r] [pl,pr](显然一开始转移区间和决策点的选择区间都为 [ 1 , n ] [1,n] [1,n]),设 m i d = L + R 2 mid=\frac{L+R}{2} mid=2L+R,我们可以先暴力求出 m i d mid mid的最优决策点 p o s pos pos,那么 [ L , m i d − 1 ] [L,mid-1] [L,mid−1]的决策点选择区间就是 [ p l , p o s ] [p_l,pos] [pl,pos], [ m i d + 1 , R ] [mid+1,R] [mid+1,R]的决策点选择区间就是 [ p o s , p r ] [pos,p_r] [pos,pr],那么这样分治下去就好了。
如果我们可以 O ( 1 ) O(1) O(1)求代价的话,对 m i d mid mid求 p o s pos pos的时间复杂度就只有 O ( n ) O(n) O(n),再加上每次要处理的区间长度会变成原本的一半,所以处理区间为n的复杂度是 f ( n ) = O ( n ) + O ( f ( n / 2 ) ) f(n)=O(n)+O(f(n/2)) f(n)=O(n)+O(f(n/2)),总复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
一个板子:
void Solve(ll l,ll r,ll pl,ll pr)
{//当前分治处理区间是[l,r],最佳决策区间是[pl,pr]ll mid=l+r>>1;ll pos;//mid的最佳决策点dp[mid]=gt(pos=pl,mid);for(int i=pl+1;i<=min(mid-1,pr);++i){if(gt(i,mid)<dp[mid]) dp[mid]=gt(pos=i,mid);}if(l<mid) Solve(l,mid-1,pl,pos);if(r>mid) Solve(mid+1,r,pos,pr);
}
JSOI2016 灯塔
大意:
给定 h i h_i hi,对于每一个 i i i,要求 ∀ j , p i ≥ h j − h i + ∣ i − j ∣ \forall j,p_i\geq h_j-h_i+\sqrt{|i-j|} ∀j,pi≥hj−hi+∣i−j∣,求最小的 p i p_i pi
思路:
不妨先将绝对值去掉,那么 p i = { m i n { h j + i − j − h i } j < i m i n { h j + j − i − h i } j > i p_i=\left\{\begin{matrix} min\{h_j+\sqrt{i-j}-h_i\} & j<i\\ min\{h_j+\sqrt{j-i}-h_i\} & j>i \end{matrix}\right. pi={min{hj+i−j−hi}min{hj+j−i−hi}j<ij>i
那么我们只要处理第一个式子就好了,第二个式子只要把整个数组反一下即可
可以发现 p i = m i n { h j + i − j − h i } p_i=min\{h_j+\sqrt{i-j}-h_i\} pi=min{hj+i−j−hi}满足决策单调性,并且 p i p_i pi之间相互无关,所以直接分治就解决了
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define IL inline
#define endl '\n'
const ll N=5e5+10;
double ep=1e-9;
const ll mod=998244353;
const ll inf=1e18;
ll n;
ll mas[N];
double dp1[N],dp2[N];
double gt(ll p,ll x)
{return mas[p]+sqrt(x-p)-mas[x];
}
void Solve(ll l,ll r,ll pl,ll pr,double *dp)
{// if(l>r||pl>pr) return;ll mid=(l+r)>>1;ll pos=pl;for(int i=pl;i<=min(mid,pr);++i){if(gt(i,mid)-dp[mid]>=-ep) dp[mid]=gt(pos=i,mid);//要取>=0}if(l<mid) Solve(l,mid-1,pl,pos,dp);if(r>mid) Solve(mid+1,r,pos,pr,dp);
}
void solve()
{cin>>n;for(int i=1;i<=n;++i) cin>>mas[i];Solve(1,n,1,n,dp1);reverse(mas+1,mas+1+n);Solve(1,n,1,n,dp2);for(int i=1;i<=n;++i){double x=fmax(dp1[i],dp2[n-i+1]);cout<<(ll)ceil(x)<<endl;}
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);// ll t;t=read();while(t--)solve();return 0;
}
SDOI2016 征途
CF321 E
类莫队做法
注意到分治要保证时间复杂度的前提是每次求 w w w代价的复杂度是 O ( 1 ) O(1) O(1)的
但是有一类 w w w比较特殊:关于区间的信息的记录,比如区间数字种类数等。这种问题我们一般可以离线下来用莫队处理,那么仿照莫队用左右端点的连续移动就可以做到 O ( 1 ) O(1) O(1)转移了,因为我们的决策点的选择范围也刚好是一个连续的区间,所以这样的话复杂度是正确的
CF868 F
CmdOI2019 任务分配问题
#include<bits/stdc++.h>
#include <iostream>
using namespace std;
#define ll int
#define IL inline
#define endl '\n'
const ll N=3e4+10;
double ep=1e-9;
const ll mod=998244353;
const ll inf=1e9;
struct Tree
{ll tr[N];#define low(x) x&(-x)void add(ll x,ll y){while(x<N){tr[x]+=y;x+=low(x);}}ll sum(ll x){ll ans=0;while(x){ans+=tr[x];x-=low(x);}return ans;}
}T;
ll n,k;
ll mas[N];
ll dp[N],pp[N];
namespace Mo
{int L=1,R=0;ll ans=0;int col[N];ll cnt[N];void add1(ll pos,ll op)//区间左边{if(op==1){ans+=T.sum(n+1)-T.sum(col[pos]);T.add(col[pos],1);}else{ans-=T.sum(n+1)-T.sum(col[pos]);T.add(col[pos],-1);}}void add2(ll pos,ll op)//区间右边{if(op==1){ans+=T.sum(col[pos]-1);T.add(col[pos],1);}else{ans-=T.sum(col[pos]-1);T.add(col[pos],-1);}}ll gt(ll l,ll r){l++;while(L<l) add1(L++,0);while(L>l) add1(--L,1);while(R>r) add2(R--,0);while(R<r) add2(++R,1);return pp[l-1]+ans;}
}
void Solve(ll l,ll r,ll pl,ll pr)
{ll mid=(l+r)>>1;ll pos;dp[mid]=Mo::gt(pos=pl,mid);for(int i=pl+1;i<=min(mid-1,pr);++i){if(dp[mid]>Mo::gt(i,mid)) dp[mid]=Mo::gt(pos=i,mid);}if(l<mid) Solve(l,mid-1,pl,pos);if(r>mid) Solve(mid+1,r,pos,pr);
}
void solve()
{cin>>n>>k;for(int i=1;i<=n;++i){cin>>Mo::col[i];}for(int i=1;i<=n;++i) pp[i]=Mo::gt(0,i);for(int i=1;i<k;++i){Solve(1,n,0,n);for(int j=1;j<=n;++j) pp[j]=dp[j];}cout<<pp[n]<<endl;}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);// ll t;t=read();while(t--)solve();return 0;
}
SMAWK
SMAWK的用途跟分治一样,用于处理同一层之间没有转移关系的情况,但是复杂度会更优,可以达到 O ( n ) O(n) O(n)
我们考虑从另一个角度来理解决策单调性
定义1 若矩阵A满足∀i,j∈[0,k],pos(i)<pos(j)则称A为单调矩阵。若A的任意子矩阵均为单调矩阵,则称A为完全单调矩阵
重新定义四边形不等式
定义2 对于n*m的矩阵A,若 ∀ 1 < i 1 < i 2 ≤ n , 1 ≤ j 1 , ≤ j 2 ≤ m \forall 1<i_1<i_2\leq n,1\leq j_1,\leq j_2\leq m ∀1<i1<i2≤n,1≤j1,≤j2≤m,均有 A i 1 , j 1 + A i 2 , j 2 ≤ A i 1 , j 2 + A i 2 , j 1 A_{i_1,j_1}+A_{i_2,j_2}\leq A_{i_1,j_2}+A_{i_2,j_1} Ai1,j1+Ai2,j2≤Ai1,j2+Ai2,j1,则称A满足四边形不等式
快速判断矩阵是否满足四边形不等式:
定理1 对于n*m的矩阵A,若 ∀ 1 < i < n , 1 ≤ j < m \forall 1<i<n,1\leq j<m ∀1<i<n,1≤j<m,均有 A i , j + A i + 1 , j + 1 ≤ A i , j + 1 + A i + 1 , j A_{i,j}+A_{i+1,j+1}\leq A_{i,j+1}+A_{i+1,j} Ai,j+Ai+1,j+1≤Ai,j+1+Ai+1,j,则称A满足四边形不等式
定理2 若矩阵A满足四边形不等式,则A以及 A T A^{T} AT是完全单调矩阵
证明就不给了,感兴趣可以去看看原论文
接下来考虑 d p i = m i n j < i { a j + w ( j , i ) } dp_i=min_{j<i}\{a_j+w(j,i)\} dpi=minj<i{aj+w(j,i)},放到矩阵上,当 j < i j<i j<i时, A i , j = d p j + w ( j , i ) , j ≥ i A_{i,j}=dp_j+w(j,i),j\geq i Ai,j=dpj+w(j,i),j≥i时,令 A i , j = i n f A_{i,j}=inf Ai,j=inf,那么求 d p i dp_i dpi其实就是在第i行找最小值对应的列j.如果矩阵是一个单调矩阵,也就是dp满足决策单调性,我们就可以采用SMAWK
SMAWK的核心内容是reduce的过程,当列数m远大于n的时候,我们的枚举量会多很多,但是其实一行只会对应一个最优列,所以大量列是多余的,reduce过程就是在去除这些冗余列
当然,为了保证一行只有一个答案,我们要限定取每一行最左边/最右边的最小值**(这一点在与WQS二分结合的时候很重要)**
其步骤如下:
-
1初始定义 k = 1;
-
2当 n ≥ m 时结束过程;否则比较 A k , k A_{k,k} Ak,k 和 A k , k + 1 A_{k,k+1} Ak,k+1
-
3若 A k , k ≥ A k , k + 1 A_{k,k}\geq A_{k,k+1} Ak,k≥Ak,k+1,删除第 k 列,k ← max(k − 1*,* 1),回到步骤 2;
-
4若 A k , k < A k , k + 1 A_{k,k}< A_{k,k+1} Ak,k<Ak,k+1 且 k = n,删除第 n + 1 列,回到步骤 2;
-
5若 A k , k < A k , k + 1 A_{k,k}< A_{k,k+1} Ak,k<Ak,k+1 且 k = n,k ← k + 1,回到步骤 2。
每一步的原因还是很显然的,这里不多赘述了。复杂度是 O ( m + n ) O(m+n) O(m+n)
为了保证线性复杂度的正确性,可以用链表实现
struct Node
{ll val,opt;Node *lst,*nxt;Node(){val=opt=0;lst=nxt=NULL;}
};
struct List
{ll len;//列表长度Node *s,*e;List(){len=0;s=new Node();s->opt=1;e=new Node();e->opt=-1;s->nxt=e;e->lst=s;}void append(ll x){//将x加入尾部Node *n=new Node();n->val=x;Node *a=e->lst;//尾部元素a->nxt=n;n->lst=a;n->nxt=e;e->lst=n;len++;//长度}List(const List &a){//Copylen=0;s=new Node();s->opt=1;e=new Node();e->opt=-1;s->nxt=e;e->lst=s;Node *n=a.s->nxt;while(n->opt!=-1){append(n->val);n=n->nxt;}}Node* del(Node *n){--len;Node *a=n->lst,*b=n->nxt;a->nxt=b,b->lst=a;delete(n);return a;//前一个节点}
};
struct submat
{List r,c;//row,column
}A;
submat Reduce(const submat &A)
{submat B;B.r=(List)A.r;B.c=(List)A.c;int n=A.r.len;//行数int m=A.c.len;//列数Node *nr=B.r.s->nxt;//第一个节点Node *nc=B.c.s->nxt;ll k=1;while(n<m){if(get(nr->val,nc->val)>get(nr->val,nc->nxt->val)){nc=B.c.del(nc);m--;if(k>1){--k;nr=nr->lst;}else{//k=1nr=B.r.s->nxt;nc=B.c.s->nxt;}}else if(k==n){B.c.del(nc->nxt);m--;}else{k++;nr=nr->nxt;nc=nc->nxt;}}return B;
}
reduce之后我们就真正开始SMAWK了
SMAWK(A) 表示计算 n × m 的完全单调矩阵 A 的每行最小值所在
列。步骤如下:
- 1若 min(n, m) = 1 直接计算答案;
- 2对Areduce,得到矩阵B,并且我们取其所有偶数行组成一个新矩阵C
- 4递归SMAWK©,得到C的每一行的最小值所在位置
- 4对于B中的奇数行,其答案在相邻两行的答案之间,那么之间暴力遍历一下即可。该步骤的复杂度为 O ( m ) O(m) O(m)
void SMAWK(submat A)
{int n=A.r.len;//行数int m=A.c.len;//列数if(n==1){ll x=A.r.s->nxt->val;//只有一行Node *nc=A.c.s->nxt;ll maxn=0;ll maxp=0;//最值位置while(nc->opt!=-1){if(get(x,nc->val)>=maxn){maxp=nc->val,maxn=get(x,nc->val);}nc=nc->nxt;}ans[x]=maxp;//存储最大值所在位置///return;}if(m==1){ll y=A.c.s->nxt->val;Node *nr=A.r.s->nxt;//第一行while(nr->opt!=-1){ans[nr->val]=y;nr=nr->nxt;}return;}submat B=Reduce(A);submat C;C.c=List(B.c);//首先保存每一列的信息Node *nr=B.r.s->nxt;//行第一个元素bool fl=0;while(nr->opt!=-1){if(fl) C.r.append(nr->val);//C保存偶数行nr=nr->nxt;fl^=1;}//得到一个只有偶数行,列不变的矩阵SMAWK(C);//递归nr=B.r.s->nxt;fl=0;Node *nc=B.c.s->nxt;//列指针while(nr->opt!=-1){if(!fl){ll z=ans[nr->nxt->val];//已经处理过了,这里是有值的ll x=nr->val;ll maxn=0,maxp=0;if(z==0) z=inf;while(nc->opt!=-1 && nc->val<=z){//枚举列//返回的是最右边的最小值if(get(x,nc->val)>=maxn){maxn=get(x,nc->val);maxp=nc->val;}nc=nc->nxt;}if(nc->lst->val==z) nc=nc->lst;ans[x]=maxp;}nr=nr->nxt;fl^=1;}
}
理论上是可以平替分治的,但是写起来太烦了。。。
有一份短一点的代码
//求每行最左边最小值
int pre[N],suf[N],M[N],n,m,ans=1e18,P=0;
vector<int>L,H;
map<int,int>Mp[N];
ll get(ll a,ll b)
{//返回第a行第b列的元素
}
int pre[N],suf[N],M[N],P=0;
void del(int x)
{if (pre[x]!=-1)suf[pre[x]]=suf[x]; else P=suf[x];pre[suf[x]]=pre[x];
}
vector<int> reduce(vector<int>X,vector<int>Y)
{for (int i=0;i<Y.size();i++) pre[i]=i-1,suf[i]=i+1;int x=0,y=0;P=0;for (int nmsl=Y.size()-X.size();nmsl>0;nmsl--){if (get(X[x],Y[y])<get(X[x],Y[suf[y]])){y=suf[y];del(pre[y]);if (x) y=pre[y],x--;} else{if (x==X.size()-1) del(suf[y]);else{y=suf[y];x++;}}}vector<int>ret;for (int i=P;i!=Y.size();i=suf[i]) ret.push_back(Y[i]);return ret;
}
void Solve(vector<int>X,vector<int>Y)
{Y=reduce(X,Y);if (X.size()==1){M[X[0]]=Y[0];return;}vector<int>Z;for (int i=0;i<X.size();i++)if (!(i%2)) Z.push_back(X[i]);Solve(Z,Y);for (int i=0;i<X.size();i++){if (!(i%2)) continue;int l=lower_bound(Y.begin(),Y.end(),M[X[i-1]])-Y.begin();int r=0;if (i==X.size()-1) r=Y.size()-1;else{r=lower_bound(Y.begin(),Y.end(),M[X[i+1]])-Y.begin();}M[X[i]]=Y[l];while (l<r){l++;if (get(X[i],Y[l])>get(X[i],M[X[i]])) M[X[i]]=Y[l];}}
}
signed main()
{cin>>n>>m;for (int i=1;i<=n;i++){H.push_back(i);}for (int i=1;i<=m;i++) L.push_back(i);Solve(H,L);
}
WQS二分+
可以与其它方法结合在一起,达到一个非常优秀的时间复杂度
关于WQS二分本身的内容这里就不多赘述了,可以去看其它文章理解一下,这里主要讲一下如何将其应用在决策单调性中
考虑一类问题:将一个序列强制分为k段,每一段 [ l , r ] [l,r] [l,r]有一个价值 w ( l , r ) w(l,r) w(l,r),问总价值最小的分段
假设将序列分为k段的最优价值为 f k f_k fk,如果所有的 ( k , f k ) (k,f_k) (k,fk)组成一个凸函数,那么我们就可以使用WQS二分了。外层枚举斜率,内层就去掉了分段的限制,就可以使用二分队列/斜率优化/分治等措施了。
现在问题还是很多的,比如凸性的证明,WQS二分的边界问题,内部求最优值时更新的取等问题等。一个一个来
WQS多解情况
多解情况是指答案K与多个点在一条线段上,此时我们无法直接通过枚举斜率来做到只切到点K,所以我们需要在枚举的同时让得到的方案具有完全的偏序,来保证我们枚举斜率的时候,知道当前斜率对应的答案是哪一点的答案,是线段左端点的,还是右端点的,从而保证后面二分调整mid的时候不会弄混淆。很重要的一点是,我们需要的只是正确的斜率,不需要保证当前斜率做出来的最优点一定是K,因为 f k = m i d ∗ k + b f_k=mid*k+b fk=mid∗k+b,其中斜率 m i d mid mid和截距 b b b都是二分之后确定的值,在同一线段上的点做出的 m i d , b mid,b mid,b都是一样的。
那么为了做到严格偏序,我们需要对每一个属性都定义大小
这篇博客讲的很清晰,我可能讲的有点抽象,可以去再看看
满足四边形不等式的序列划分问题的答案凸性以及WQS二分的方案构造
这里我们尝试证明满足四边形不等式的序列划分问题都具有凸性
不妨先来看另一个问题:给定一张n个点的DAG,点 i i i与点 j j j当 i < j i<j i<j时有权值 w ( i , j ) w(i,j) w(i,j),问从1走到n的经过k条边的最短路
简单转化一下,这个问题的dp方程就是我们熟悉的: d p i , j = m i n { d p k , j − 1 + w ( k , i ) } dp_{i,j}=min\{dp_{k,j-1}+w(k,i)\} dpi,j=min{dpk,j−1+w(k,i)}, d p i , j dp_{i,j} dpi,j表示走到i,经过j条边的最短路
设 f k f_k fk表示经过k条边的最短路
下面我们尝试证明:当权值矩阵w满足四边形不等式的时候, f k f_k fk是一个下凸函数。换句话说, ∀ k ∈ [ 2 , n − 2 ] \forall k\in[2,n-2] ∀k∈[2,n−2], f k + 1 − f k > f k − f k − 1 f_{k+1}-f_k>f_k-f_{k-1} fk+1−fk>fk−fk−1
引理:∀1 ≤ s < r < t ≤ n − 1*,* f(s) + f(t) ≥ f(r) + f(s + t − r)
如果我们能够证明该引理的话,带入 s = k − 1 , r = k , t = k + 1 s=k-1,r=k,t=k+1 s=k−1,r=k,t=k+1,则命题得证
下面尝试证明引理:
不妨记 f s f_s fs对应的最优方案是 p 1 , p 2 . . . p s + 1 p_1,p_2...p_{s+1} p1,p2...ps+1, f t f_t ft对应的最优方案是 q 1 , q 2 , . . q t + 1 q_1,q_2,..q_{t+1} q1,q2,..qt+1
记 v = r − s > 0 v=r-s>0 v=r−s>0,如果我们能够找到 i ∈ [ 1 , s ] i \in [1,s] i∈[1,s],满足 p i ≤ q i + v < q i + v + 1 ≤ p i + 1 ( i + v + 1 ≤ s + v + 1 ≤ t ) p_i\leq q_{i+v}<q_{i+v+1}\leq p_{i+1}(i+v+1\leq s+v+1\leq t) pi≤qi+v<qi+v+1≤pi+1(i+v+1≤s+v+1≤t),就能够构造路径 R 1 : R_1: R1: p 1 , . . . p i , q i + v + 1 , q i + v + 2 , . . q t + 1 p_1,...p_i,q_{i+v+1},q_{i+v+2},..q_{t+1} p1,...pi,qi+v+1,qi+v+2,..qt+1,以及路径 R 2 : R_2: R2: q 1 , . . . q i + v , p i + 1 , p i + 2 , . . . p s + 1 q_1,...q_{i+v},p_{i+1},p_{i+2},...p_{s+1} q1,...qi+v,pi+1,pi+2,...ps+1(也就是把两段路径的后半段交换了一下,并且保证一定交换了一部分)
两段路径的长度分别是 i − 1 + ( t + 1 − i − v − 1 ) + 1 = t − v = t − r + s i-1+(t+1-i-v-1)+1=t-v=t-r+s i−1+(t+1−i−v−1)+1=t−v=t−r+s,以及 i − 1 + v + ( s + 1 − i − 1 ) + 1 = s + v = r i-1+v+(s+1-i-1)+1=s+v=r i−1+v+(s+1−i−1)+1=s+v=r,
那么由f的定义, R 1 R_1 R1的路径长度 l e n ( R 1 ) ≥ f t − r + s len(R_1) \geq f_{t-r+s} len(R1)≥ft−r+s, R 2 R_2 R2的路径长度 l e n ( R 2 ) ≥ f r len(R_2)\geq f_{r} len(R2)≥fr,
由四边形不等式 w ( p i , q i + v + 1 ) + w ( q i + v , p i + 1 ) ≤ w ( q i + v + q i + v + 1 ) + w ( p i , p i + 1 ) w(p_i,q_{i+v+1})+w(q_{i+v},p_{i+1})\leq w(q_{i+v}+q_{i+v+1})+w(p_i,p_{i+1}) w(pi,qi+v+1)+w(qi+v,pi+1)≤w(qi+v+qi+v+1)+w(pi,pi+1)
故 f s + f t ≥ l e n ( R 1 ) + l e n ( R 2 ) ≥ f t − r + s + f r f_s+f_t\geq len(R_1)+len(R_2)\geq f_{t-r+s}+f_r fs+ft≥len(R1)+len(R2)≥ft−r+s+fr
第一个不等式是因为 R 1 , R 2 R_1,R_2 R1,R2与原本路径的区别只有中间衔接的一段
由上,我们只要证明存在这样的一个 i i i即可。
不妨记路径P将 ( 1 , n ] (1,n] (1,n]分成了s个部分,其中第i个部分是 ( p i , p i + 1 ] (p_i,p_{i+1}] (pi,pi+1]
我们记 a i a_i ai表示 q i + v q_{i+v} qi+v在哪一段,那么如果存在 i i i, a i = a i + 1 = k a_i=a_i+1=k ai=ai+1=k,我们就找到答案为k了。
记 b i = a i − i b_i=a_i-i bi=ai−i,显然 b 1 ≥ 0 , b s + 1 ≤ − 1 b_1\geq 0,b_{s+1}\leq -1 b1≥0,bs+1≤−1,后者是因为 a s + 1 − ( s + 1 ) ≤ s − ( s + 1 ) = − 1 a_{s+1}-(s+1)\leq s-(s+1)=-1 as+1−(s+1)≤s−(s+1)=−1,此外显然 b i − b i − 1 = 0 b_i-b_{i-1}=0 bi−bi−1=0或 − 1 -1 −1,故 b i − b i − 1 ≥ − 1 b_i-b_{i-1}\geq -1 bi−bi−1≥−1
由此序列b中一定存在一个-1,取最靠前的-1,记为 b i + 1 b_{i+1} bi+1.它前面一定是 b i = 0 b_i=0 bi=0,故 a i = a i + 1 a_i=a_{i+1} ai=ai+1
这样我们就找到了一个合法的 i i i,引理得证,故命题得证 Q.E.D
这段证明还是非常玄妙(玄幻)的。当然它对于我们的方案构造也有帮助
WQS二分中有时候会存在要求为k,但是 k > l , k < r k>l,k<r k>l,k<r且 l , r , k l,r,k l,r,k在一条线段上的情况,这时候我们一般通过限定边数尽可能多/少来保证取到线段的端点。但是想要构造方案的话就会不知所措了
我们记答案斜率为mid,这条包含答案k的线段的端点为 ( l , f l ) , ( r , f r ) (l,f_l),(r,f_r) (l,fl),(r,fr),则 ∀ i ∈ [ l , r ] , f i = f l + m i d ∗ ( i − l ) \forall i\in[l,r],f_i=f_l+mid*(i-l) ∀i∈[l,r],fi=fl+mid∗(i−l)
我们可以先把 l , r l,r l,r对应的最优方案找出来,长度分别为 l , r l,r l,r,那么按照上述证明中的构造方式我们得到长度为 k , l + r − k k,l+r-k k,l+r−k的路径 R 1 , R 2 R_1,R_2 R1,R2,记其路径长度分别为 l e n ( R 1 ) = a , l e n ( R 2 ) = b len(R_1)=a,len(R_2)=b len(R1)=a,len(R2)=b,有 a ≥ f l + m i d ∗ ( k − l ) , b ≥ f l + m i d ∗ ( l + r − k − l ) a\geq f_l+mid*(k-l),b\geq f_l+mid*(l+r-k-l) a≥fl+mid∗(k−l),b≥fl+mid∗(l+r−k−l)
且有 2 f l + m i d ∗ ( k − l ) + m i d ∗ ( l + r − k − l ) = 2 f l + m i d ∗ ( r − l ) ≤ a + b ≤ f l + f r = 2 f l + m i d ∗ ( r − l ) 2f_l+mid*(k-l)+mid*(l+r-k-l)=2f_l+mid*(r-l)\leq a+b\leq f_l+f_r=2f_l+mid*(r-l) 2fl+mid∗(k−l)+mid∗(l+r−k−l)=2fl+mid∗(r−l)≤a+b≤fl+fr=2fl+mid∗(r−l)
第二个不等号的原因见证明片段
发现 a + b a+b a+b被边界夹住了,故 a = f l + m i d ∗ ( k − l ) a= f_l+mid*(k-l) a=fl+mid∗(k−l),由此a就是 f k f_{k} fk,我们就得到了长度为k的构造方案
以上内容参考 《The d-Edge Shortest-Path Problem for a Monge Grapll》,以及APIO2021《决策单调性与四边形不等式》
WQS外层二分时的边界
一般来说直接取 [ − i n f , i n f ] [-inf,inf] [−inf,inf]即可,或者稍微算一下卡住边界
不管是这样的
还是这样的
只要边界范围足够就不会有问题。但是有一类分段问题,图像长这样
分段为0的时候,总价值为0,然后开始分段之后价值随分段减少。不考虑0的话,后面的一段也是满足凸函数的,那么这个对我们的边界会有影响吗?个人感觉没有,因为我们只要保证wqs二分之后在做最优决策的时候保证不让段数为0即可
比如这题就是这个情况CF321 E (在分治部分此题作为练习出现,当然它也可以WQS二分,毕竟它满足四边形不等式,而我们已经证明了该类问题的凸性)
那么到这里,理论部分就差不多完善了,我们就可以看看应用了
不妨就看看这题CF321 E
按照套路,我们外层枚举斜率,内层就是每一段的贡献要减去一个 m i d mid mid,问最优分段数及其对应的总贡献。dp式满足决策单调性,可以直接上单调队列,复杂度是 O ( n l o g n 2 ) O(nlogn^2) O(nlogn2),外层 O ( l o g ) O(log) O(log),内层 O ( n l o g ) O(nlog) O(nlog),可以看到比普通的 O ( n 2 l o g ) O(n^2log) O(n2log)分治要优化了不少
code
#include <bits/stdc++.h>
#define pii pair<int,int>
#define il inline
#define ll long long
using namespace std;
const int N=4010;
const ll inf=1e18;
ll n,m;
ll mp[N][N];
ll cnt[N];
ll sum[N][N];
ll dp[N];
ll que[N];
ll ls[N],rs[N];
ll ANS;
ll cal(ll l,ll r)
{//l+1->rreturn sum[r][r]-sum[l][r]-sum[r][l]+sum[l][l];
}
ll gt(ll k,ll x,ll val)
{return dp[k]+cal(k,x)-val;
}
bool judge(ll x)
{ll hd=1,tl=0;que[++tl]=0;ls[0]=1,rs[0]=n;for(int i=1;i<=n;++i){cnt[i]=0,ls[i]=rs[i]=0;while(hd<tl&&rs[que[hd]]<i) hd++;dp[i]=gt(que[hd],i,x);cnt[i]=cnt[que[hd]]+1;while(hd<tl&>(i,ls[que[tl]],x)<gt(que[tl],ls[que[tl]],x)) tl--;ll L=ls[que[tl]],R=n+1;while(L<=R){ll mid=(L+R)>>1;if(gt(i,mid,x)<=gt(que[tl],mid,x)) R=mid-1;else L=mid+1;}// cout<<i<<" "<<que[tl]<<' '<<R+1<<" "<<gt(i,R+1,x)<<" "<<gt()ll p_ans=R+1;if(p_ans>n) continue;rs[que[tl]]=p_ans-1;que[++tl]=i;ls[i]=p_ans,rs[i]=n;}ANS=dp[n];
// cout<<x<<" "<<cnt[n]<<" "<<ANS<<" "<<ANS+m*x<<endl;return cnt[n]>=m;//尽可能分多段,尽可能选靠后的点转移
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){cin>>mp[i][j];if(i>j) mp[i][j]=0;}}for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){sum[i][j]=sum[i][j-1]+sum[i-1][j]+mp[i][j]-sum[i-1][j-1];}}ll l=-1e18,r=0;while(l<=r){ll mid=(l+r)>>1;if(judge(mid)) r=mid-1;else l=mid+1;}judge(r+1);cout<<ANS+m*(r+1)<<endl;
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);solve();return 0;
}
IOI2000 邮局 加强版
SDOI2016 征途
套路都差不多,套一个WQS的事,内层看情况用不同的优化手段
Akvizna
你面临 n 名参赛者的挑战,最终要将他们全部战胜。
每一轮中,都会淘汰一些选手;你会得到这一轮奖金池中 被淘汰者 除以 这一轮对手总数 比例的奖金。
例如某一轮有 10 个对手,淘汰了 3 个,那么你将获得奖金池中 3/10 的奖金。
假设每一轮的奖金池均为一元,Mirko
希望通过恰好 k 轮赢得比赛,那么他最多可能获得多少奖金呢?
你只需要输出答案保留 9 位小数即可。
这题略阴间,二分的时候不枚举小数的话过不了,肥肠卡精度,不过思维难度一般
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define double long double
#define endl '\n'
const double eps=1e-12;
const ll inf=1e18;
const ll N = 1e5+10;
int n,m;
double dp[N];
double ANS;
double inv[N];
ll cnt[N];//分的段数
ll que[N];
ll ls[N],rs[N];
double gt(ll k,ll x)
{return dp[k]+(double)((x-k)*1.0*inv[n-k]);
}
bool judge(double x)
{ll hd=1,tl=0;que[++tl]=0;ls[0]=1,rs[0]=n;for(int i=1;i<=n;++i){cnt[i]=0,ls[i]=rs[i]=0;while(hd<tl&&rs[que[hd]]<i) hd++;cnt[i]=cnt[que[hd]]+1;dp[i]=gt(que[hd],i)-x;// cout<<i<<" "<<que[hd]<<' '<<dp[i]<<endl;while(hd<tl&>(i,ls[que[tl]])>=gt(que[tl],ls[que[tl]])) tl--;ll L=ls[que[tl]],R=n+1;while(L<=R){ll mid=(L+R)>>1;if(gt(i,mid)>=gt(que[tl],mid)) R=mid-1;else L=mid+1;}ll p_ans=R+1;if(p_ans>n) continue;rs[que[tl]]=p_ans-1;que[++tl]=i;ls[i]=p_ans,rs[i]=n;}ANS=dp[n];// cout<<x<<' '<<cnt[n]<<' '<<ANS<<' '<<ANS+m*x<<endl;return cnt[n]<=m;
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;++i) inv[i]=1.0/(i*1.0);// for(int i=1;i<=n;++i)// {// for(int j=1;j<=min(i,m);++j)// {// for(int k=j-1;k<i;++k)// {// dp[i][j]=max(dp[i][j],dp[k][j-1]+(double)((i-k)*1.0/(n-k)));// }// }// }// judge(0);// for(double i=-2;i<=2;i+=0.1) judge(i);double l=-100,r=100;while(l+eps<=r){double mid=(l+r)/2.0;if(judge(mid)) r=mid;else l=mid;}judge(r);// cout<<r+1<<endl;cout<<fixed<<setprecision(9)<<ANS+m*1.0*(r)<<endl;
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);solve();return 0;
}
Tips:
做WQS二分一定要注意问题函数是一个上凸包还是下凸包
注意左右边界
有时候也是也是要二分小数的!
斜率优化
基本的斜率优化本人已经在另一篇博客中讲的很详细了,从入门到精通应该都有了。
然后更多的应用大概就是与WQS二分结合了吧
注意点好像也没啥,毕竟WQS二分之后内部就是一个纯纯的一维dp
SDOI2016 征途
Akvizna
值得一提的还有这道题JSOI2001 柠檬
之前在二分栈里提过它,但其实它也可以用斜率优化做,但是因为斜率实际上是递减的,所以内部维护凸包的时候是用一个栈(因为最优点在最后了,放入点也是在最后),这个还是比较少见的
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define IL inline
#define endl '\n'
const ll N=100010;
const ll mod=998244353;
const ll inf=1e18;
inline int read()
{int X=0; bool flag=1; char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}if(flag) return X;return ~(X-1);
}
ll n,a;
ll dp[N];
vector<ll> col[N],st[N];//栈
ll gt(ll x,ll c)
{return dp[col[c][x]-1]+c*x*x-2*x*c;
}
double Slope(ll a,ll b,ll col)
{if(a==b) return inf;double x=gt(a,col)-gt(b,col);x=x*1.0;double y=a-b;y*=1.0;return x/y;
}
void solve()
{cin>>n;for(int i=1;i<=10000;++i) col[i].push_back(0);for(int i=1;i<=n;++i){cin>>a;ll len=col[a].size();col[a].push_back(i);//放入idif(len==1) st[a].push_back(1);//st存的是横坐标(颜色前缀和)else{//维护上凸包while(st[a].size()>1&&Slope(st[a][st[a].size()-2],st[a][st[a].size()-1],a)<=2*a*len) st[a].pop_back();//sum_i=lenll id=st[a][st[a].size()-1];dp[i]=dp[col[a][id]-1]+a*(1+len-id)*(1+len-id);while(st[a].size()>1&&Slope(st[a][st[a].size()-2],st[a][st[a].size()-1],a)<=Slope(st[a][st[a].size()-1],len,a)) st[a].pop_back();st[a].push_back(len);}dp[i]=max(dp[i],dp[i-1]+a);}cout<<dp[n]<<endl;
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);// ll t;t=read();while(t--)solve();return 0;
}
一些特殊情况
可以看到,如果题目已经有了决策单调性,大部分情况下还是比较套路的,但是有些时候问题在全局上不满足决策单调性并不意味着局部也没有。
还是JSOI2001 柠檬这道题,它就是在同种颜色内部满足决策单调性(这么一看这真是道好题啊,哪哪都这么与众不同)
以及2016NAIP H
大意:
有n个物品,每个物品有一个体积 w i w_i wi和价值 v i v_i vi,现在要求对 V ∈ [ 1 , m ] V∈[1,m] V∈[1,m],求出体积为 V V V的
背包能够装下的最大价值
1 ≤ n ≤ 1000000 ; 1 ≤ m ≤ 100000 ; 1 ≤ w i ≤ 300 ; 1 ≤ v i ≤ 1 0 9 1≤n≤1000000;1≤m≤100000;1≤w_i≤300;1≤v_i≤10^9 1≤n≤1000000;1≤m≤100000;1≤wi≤300;1≤vi≤109
其实就是对每一个 V V V,做一个多重背包
思路:
发现每一个物品的体积都比较小,所以可以按照体积分类。那么对于同一种体积的物品,我们肯定贪心选择价值最大的,所以可以排个序
考虑 d p i , j dp_{i,j} dpi,j表示使用体积 ≤ i \leq i ≤i的物品,总体积为j的最大价值。我们可以将所有需要更新的体积按照%i来重新编号。比如当前i是2,m是9,我们就可以将 0 , 2 , 4 , 6 , 8 0,2,4,6,8 0,2,4,6,8化为一类各自重新编号为 0 , 1 , 2 , 3 , 4 0,1,2,3,4 0,1,2,3,4, 1 , 3 , 5 , 7 , 9 1,3,5,7,9 1,3,5,7,9划为一类,编号同理
这样的好处就是我们对于每一个i,j的范围也只有 [ 0 , i ] [0,i] [0,i]这么大了,以及同一组体积内部的差恰好为i,那么物品就可以直接按照价值大小贪心塞了。
那么此时dp的意义就变了。如果当前是在更新%i=a的体积,则 d p i , j dp_{i,j} dpi,j表示使用体积 ≤ i \leq i ≤i的物品,总体积为 j ∗ i + a j*i+a j∗i+a的最大值
%i=a时, d p i , j = m a x k ≤ j { d p i − 1 , k + w ( k , j ) } dp_{i,j}=max_{k\leq j}\{dp_{i-1,k}+w(k,j)\} dpi,j=maxk≤j{dpi−1,k+w(k,j)},其中 w ( k , j ) w(k,j) w(k,j)表示体积=i的物品中最大的 j − k j-k j−k个物品的价值和,记为前缀和 v t i , j − k vt_{i,j-k} vti,j−k
简单证一下四边形不等式:
考虑 i , i + 1 , j , j + 1 , i + 1 < j i,i+1,j,j+1,i+1<j i,i+1,j,j+1,i+1<j
w ( i , j ) + w ( i + 1 , j + 1 ) = 2 v t j − i w(i,j)+w(i+1,j+1)=2vt_{j-i} w(i,j)+w(i+1,j+1)=2vtj−i
w ( i + 1 , j ) + w ( i , j + 1 ) = v t j − i − 1 + v t j − i + 1 = 2 v t j − i + a j − i + 1 − a j − i − 1 ≤ w ( i , j ) + w ( i + 1 , j + 1 ) w(i+1,j)+w(i,j+1)=vt_{j-i-1}+vt_{j-i+1}=2vt_{j-i}+a_{j-i+1}-a_{j-i-1}\leq w(i,j)+w(i+1,j+1) w(i+1,j)+w(i,j+1)=vtj−i−1+vtj−i+1=2vtj−i+aj−i+1−aj−i−1≤w(i,j)+w(i+1,j+1)
得证
那么直接分治即可
code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define double long double
#define endl '\n'
const double eps=1e-11;
const ll inf=1e18;
const ll N = 1e6+10;
ll n,m;
vector<ll> vt[310];
ll dp[N],pp[N];
ll gt(ll id,ll x,ll mod,ll res)
{return pp[id*mod+res]+vt[mod][x-id-1];
}
bool cmp(ll a,ll b)
{return a>b;
}
void Solve(ll l,ll r,ll pl,ll pr,ll mod,ll res)
{if(l>r) return;ll mid=(l+r)>>1;ll pos=mid;dp[mid*mod+res]=pp[mid*mod+res];for(int i=min(mid-1,pr);i>=pl;--i){if(mid-i>(int)vt[mod].size()) break;if(gt(i,mid,mod,res)>dp[mid*mod+res]) dp[mid*mod+res]=gt(pos=i,mid,mod,res);}Solve(l,mid-1,pl,pos,mod,res);Solve(mid+1,r,pos,pr,mod,res);
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;++i){ll a,b;cin>>a>>b;vt[a].push_back(b);}for(int i=1;i<=300;++i){sort(vt[i].begin(),vt[i].end(),cmp);for(int j=1;j<(int)vt[i].size();++j) vt[i][j]+=vt[i][j-1];}for(int i=1;i<=300;++i){if(!vt[i].size()) continue;//枚举物品体积的类别for(int j=0;j<i;++j){//枚举%i=j的体积Solve(0,(m-j)/i,0,(m-j)/i,i,j);}for(int j=1;j<=m;++j) dp[j]=max(dp[j],dp[j-1]);for(int j=1;j<=m;++j) pp[j]=max(dp[j],dp[j-1]);}for(int i=1;i<=m;++i) cout<<pp[i]<<" ";
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);solve();return 0;
}
本来想用SMAWK写的,但是一直MLE,不懂,求懂的佬教教
当然这题的决策单调性也是分组才有的,很抽象,感觉本人是不可能看出来的(哭
总结:
大工程,希望对自己&大家有用💗
参考文章
OIWIKI
Bein, W W, Larmore, L L, and Park, J K. The d-edge shortest-path problem for a Monge graph. United States: N. p., 1992. Web.
Bein, W W, Brucker, P, and Park, J K. Applications of an algebraic Monge property. United States: N. p., 1993. Web.
DP的决策单调性优化总结
Divide and Conquer DP
决策单调性 - MCAdam
关于决策单调性与图像的结合
彭思进 《决策单调性与四边形不等式》
相关文章:

【学习笔记】各类基于决策单调性的dp优化
文章目录 对于决策单调性的一般解释关于决策单调性的证明四边形不等式一维dp区间dp一种二维dp一些满足四边形不等式的函数类 与图形相结合 决策单调性的常见优化手段二分队列二分栈分治类莫队做法 SMAWKWQS二分WQS多解情况满足四边形不等式的序列划分问题的答案凸性以及WQS二分…...

【C++】构造函数初始化列表 ⑤ ( 匿名对象 生命周期 | 构造函数 中 不能调用 构造函数 )
文章目录 一、匿名对象 生命周期1、匿名对象 生命周期 说明2、代码示例 - 匿名对象 生命周期 二、构造函数 中调用 构造函数1、构造函数 中 不能调用 构造函数2、代码示例 - 构造函数中调用构造函数 构造函数初始化列表 总结 : 初始化列表 可以 为 类的 成员变量 提供初始值 ;…...
Knife4j系列--使用方法
原文网址:Knife4j系列--使用/教程/实例/配置_IT利刃出鞘的博客-CSDN博客...

pmp项目管理考试是什么?适合哪些人学?
PMP,简单点说,就是美国PMI为考察项目管理人士的专业能力而设立的考试。 该流程以知识和任务驱动型指南评估从业者的能力,同时确定项目经理能力行业标准,包括各项知识、任务和技能的特点、重要性与运用频率。(考纲原文…...

CSDN博客可以添加联系方式了
csdn博客一直不允许留一些联系方式,结果是官方有联系方式路径 在首页,往下拉,左侧就有 点击这个即可添加好友了~ 美滋滋,一起交流, 学习技术 ~...

小程序隐私弹窗的实现
小程序的开发者对于微信官方来说是有爱有恨,三天二头整事是鹅厂的一贯风格。 隐私弹窗的几个要点 回归正题,小程序隐私弹窗的几个要点: 1、何时弹出用户隐私协议的弹窗? 2、是每次进小程序都弹出来吗? 这两个想明…...

【JavaEE】多线程案例-单例模式
文章目录 1. 前言2. 什么是单例模式3. 如何实现单例模式3.1 饿汉模式3.2 懒汉模式4. 解决单例模式中遇到的线程安全问题4.1 加锁4.2 加上一个判断解决频繁加锁问题4.2 解决因指令重排序造成的线程不安全问题 1. 前言 单例模式是我们面试中最常考到的设计模式。什么是设计模式呢…...

社区分享|MeterSphere变身“啄木鸟”,助力云帐房落地接口自动化测试
云帐房网络科技有限公司(以下简称为“云帐房”)成立于2015年3月,以“成为最值得信赖的税务智能公司”为愿景,运用人工智能、大数据等互联网技术,结合深厚的财税行业服务经验,为代账公司和中大型企业提供智能…...

fpga内嵌逻辑分析仪使用方法
文章目录 前言一、方法1 — 使用 IP 核创建 ILA 调试环境1、创建 ILA ip 核2、进行例化3、生成比特流文件4、下载程序5、进行在线调试 二、方法2 — 使用 Debug 标记创建 ILA1、Debug 标记相关信号2、综合操作3、设置 Set Up Debug4、生成比特文件5、下载程序6、进行在线调试 前…...

第14章 结构和其他数据形式
本章介绍以下内容: 关键字:struct、union、typedef 运算符:.、-> 什么是C结构,如何创建结构模板和结构变量 如何访问结构的成员,如何编写处理结构的函数 联合和指向函数的指针 设计程序时,最重要的步骤之…...

vue 把echarts封装成一个方法 并且从后端读取数据 +转换数据格式 =动态echarts 联动echarts表
1.把echarts 在 methods 封装成一个方法mounted 在中调用 折线图 和柱状图 mounted调用下边两个方法 mounted(){//最早获取DOM元素的生命周期函数 挂载完毕console.log(mounted-id , document.getElementById(charts))this.line()this.pie()},methods里边的方法 line() {// …...
Python基础08 面向对象的基本概念
Python使用类(class)和对象(object),进行面向对象(object-oriented programming,简称OOP)的编程。 面向对象的最主要目的是提高程序的重复使用性。我们这么早切入面向对象编程的原因是,Python的整个概念是基于对象的。…...

APP自动化之Poco框架
今天给大家介绍一款自动化测试框架Poco,其脚本写法非常简洁、高效,其元素定位器效率更快,其本质基于python的第三方库,调试起来也会非常方便,能够很好的提升自动化测试效率,节省时间。 (一)背景…...
c++拷贝构造【显式调用】和运算符=重载构造【隐式调用】解析
深拷贝 vs. 浅拷贝 深拷贝:开辟新内存,独立对象,堆区浅拷贝:共享内存,引用对象,栈区 深拷贝:深拷贝是一种拷贝方式,它会在堆区重新分配内存并复制对象的内容。 这意味着原对象和新…...

无涯教程-JavaScript - LCM函数
描述 LCM函数返回整数的最小公倍数。最小公倍数是最小的正整数,它是所有整数参数number1,number2等的倍数。使用LCM添加具有不同分母的分数。 语法 LCM (number1, [number2] ...)争论 Argument描述Required/OptionalNumber1, number2... 您想要最小公倍数的1到255个值。 如…...

Java多线程篇(3)——线程池
文章目录 线程池ThreadPoolExecutor源码分析1、如何提交任务2、如何执行任务3、如何停止过期的非核心线程4、如何使用拒绝策略 ScheduledThreadPoolExecutor源码分析 线程池 快速过一遍基础知识 7大参数 corePoolSize : 核心线程数 maximumPoolSize: 最…...
那些年我们遇到过的关于excel的操作
本文为直接从百度上搜索的关于excel的函数使用,方便以后用,希望会持续补充 excel中筛选出两列重复的数据【场景:A、B两列数据个数不同且无序,想找出A列中的数据在B列中不存在的,通过比较后单元格为空的代表该行不存在的…...

Angular变更检测机制
前段时间遇到这样一个 bug,通过一个 click 事件跳转到一个新页面,新页面迟迟不加载; 经过多次测试发现,将鼠标移入某个 tab ,页面就加载出来了。 举个例子,页面内容无法加载,但是将鼠标移入下图…...

Redis之String类型
文章目录 Redis之String类型1. 赋值/获取值2. 同时设置/获取多个键值3. 数值增减4. 获取字符串长度5. 向尾部追加值6. 分布式锁7.应用场景 Redis之String类型 Redis命令不区分大小写 1. 赋值/获取值 赋值:set key value 取值:get key (当键不存在时候&…...
使用redis中的zset实现滑动窗口限流
使用redis和zset实现滑动窗口限流 文章目录 使用redis和zset实现滑动窗口限流Zset**初始化一个ZSet**:其中包含所有用户的ID和时间戳。**添加元素到ZSet**:当用户发起请求时,将当前时间戳和用户ID作为元素添加到ZSet中。**删除过期的元素**&a…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...