dp凸优化

P2619 [国家集训队]Tree I

题意: V V V个点 E E E条边的无向带权图,每条边的颜色为黑色或白色。求恰好有 n e e d need need条白边的生成树中权值最小的生成树。

设选择 x x x条白边的答案为 f ( x ) f(x) f(x)
首先,根据 k r u s k a l kruskal kruskal直接生成一颗最小生成树,假设有 x x x条白边,如果要减少到 x − 1 x-1 x1条,则需要删除在前的一条白边,加入一条边权等于或更大的黑边。如果要增加到 x + 1 x+1 x+1条,同样需要删除一条黑边,加入一条边权等于或更大的白边。
从而, f ( x ) f(x) f(x)是一个下凹函数。
把点数看作 x x x轴, f ( x ) f(x) f(x)的值看作 y y y轴, f ( x ) f(x) f(x)的图像大致如下:
在这里插入图片描述

如果分别对点 x = { 0 , 1 , . . . , ∞ } x=\{0,1,...,\infty\} x={0,1,...,}做切线,可以发现切线的斜率从左到右单调递增,如图分别做点 A , D , G A,D,G A,D,G的切线。
在这里插入图片描述
此外,在斜率相同、截距不同的所有与 f ( x ) f(x) f(x)有交点的直线中,与 f ( x ) f(x) f(x)相切的那条直线截距最短。
在这里插入图片描述
从而假设我们知道了选 x x x个白点斜率,我们只需要最小化直线的截距。
直线的方程为 y = k ∗ x + b y=k*x+b y=kx+b,令曲线 f ( x ) f(x) f(x)和直线相交,有 f ( x ) = y = k ∗ x + b → b = f ( x ) − k ∗ x f(x)=y=k*x+b\rightarrow b=f(x)-k*x f(x)=y=kx+bb=f(x)kx,为了求得截距 b b b,给所有白点的边权都 − k -k k,则选 x x x个点恰好给 b b b带来 − k ∗ x -k*x kx的贡献,这样去做最小生成树就解除了要选固定数量的白边的限制。

不知道选 x x x个白点的斜率怎么办?我们可以二分 k k k,在做最小生成树的时候统计使用的白点的数量,如果白点个数少了,则增大斜率,如果多了,则减小斜率。当统计的数量恰好为白点的个数,再由 f ( x ) = b + k ∗ x f(x)=b+k*x f(x)=b+kx还原恰好选择 x x x点的代价。这种方法就是传说中的wqs二分。

此外,如果 k k k是个浮点数怎么办?为了避免浮点,可以在排序的时候权值相同则让白点优先排在前面。这样把二分的条件改成如果点数 ≥ n e e d \ge need need则更新答案为 b + n e e d ∗ k b+need*k b+needk

#include<bits/stdc++.h>
typedef long long ll;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define nep(i,r,l) for(int i=r;i>=l;i--)
void sc(int &x){scanf("%d",&x);}
void sc(int &x,int &y){scanf("%d%d",&x,&y);}
void sc(int &x,int &y,int &z){scanf("%d%d%d",&x,&y,&z);}
void sc(char *s){scanf("%s",s);}
void out(int x){printf("%d\n",x);}
void out(ll x){printf("%lld\n",x);}
void out(int x,int y){printf("%d %d\n",x,y);}
void out(ll x,ll y){printf("%lld %lld\n",x,y);}
void out(int x,int y,int z){printf("%d %d %d\n",x,y,z);}
void out(ll x,ll y,ll z){printf("%lld %lld %lld\n",x,y,z);}
using namespace std;
const int N=1e5+5;
int n,m,q;
struct node
{
    int u,v,w,c;
    bool operator<(const node&o)const
    {
        if(w==o.w) return c<o.c;
        return w<o.w;
    }
}e[N],e2[N];
int f[N];
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
pair<int,int>sol(int k)
{
    rep(i,1,m)
        if(e[i].c==0) e[i].w-=k;
    sort(e+1,e+1+m);
    rep(i,1,n) f[i]=i;
    int ans=0,res=0;
    rep(i,1,m)
    {
        int fu=getf(e[i].u),fv=getf(e[i].v);
        if(fu==fv) continue;
        f[fu]=fv;
        ans+=e[i].c==0;
        res+=e[i].w;
    }
    rep(i,1,m)
        if(e[i].c==0) e[i].w+=k;
    return {ans,res};
}
int main()
{
    //freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    sc(n,m,q);
    rep(i,1,m)
        sc(e[i].u,e[i].v,e[i].w),sc(e[i].c),e[i].u++,e[i].v++;
    int l=-100,r=100,ans;
    while(l<=r)
    {
        int m=l+r>>1;
        pair<int,int>x=sol(m);
        if(x.first>=q) ans=x.second+q*m,r=m-1;
        else l=m+1;
    }
    out(ans);
}

P4767 [IOI2000]邮局

f ( x ) f(x) f(x)为建立了 x x x个邮局的最小代价,有 f ( x ) ≤ f ( x − 1 ) f(x)\le f(x-1) f(x)f(x1) f ( x − 1 ) − f ( x ) ≥ f ( x ) − f ( x + 1 ) f(x-1)-f(x)\ge f(x)-f(x+1) f(x1)f(x)f(x)f(x+1),从而 f ( x ) f(x) f(x)的图像也为一个单调下凹的函数。同样可以二分斜率 k k k,给每建立一个邮局的花费增加 − k -k k,就可以去掉恰好选 P P P个邮局的限制,就可以用 O ( W 2 ) O(W^2) O(W2) d p dp dp解决。总的时间复杂度为 O ( W 2 l o g A ) O(W^2logA) O(W2logA)
此外,还可以发现这个 O ( W 2 ) O(W^2) O(W2) d p dp dp可以用四边形不等式优化,将时间复杂度降到 O ( W l o g W ) O(WlogW) O(WlogW)。总的时间复杂度可化为 O ( W l o g W l o g A ) O(WlogWlogA) O(WlogWlogA)
在这里插入图片描述

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=3005;
int n,m,a[N],sum[N];
int cost(int l,int r)
{
    int m=l+r>>1;
    return sum[r]-sum[m]-a[m]*(r-m)+a[m]*(m-l+1)-sum[m]+sum[l-1];
}
pair<int,int>dp[N];
pair<int,int>cal(int i,int j,int k)
{
    if(i>j) return {inf,inf};
    return {dp[i].first+cost(i+1,j)-k,dp[i].second+1};
}
pair<int,int> sol(int k)
{
    memset(dp,inf,sizeof(dp));
    dp[0]={0,0};
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++)
        dp[i]=min(dp[i],cal(j,i,k));
    return dp[n];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    int l=-10000,r=10000,ans;
    while(l<=r)
    {
        int mid=l+r>>1;
        pair<int,int>x=sol(mid);
        if(x.second<=m) ans=x.first+m*mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",ans);
}

四边形不等式

四边形不等式参考文章
用四边形不等式优化 d p dp dp需要 c o s t i , j cost_{i,j} costi,j满足:

  1. c o s t i , j + c o s t i + 1 , j + 1 ≤ c o s t i , j + 1 + c o s t i + 1 , j ( i < j ) cost_{i,j}+cost_{i+1,j+1}\le cost_{i,j+1}+cost_{i+1,j}(i<j) costi,j+costi+1,j+1costi,j+1+costi+1,j(i<j)
  2. c o s t a , d ≥ c o s t b , c ( a ≤ b ≤ c ≤ d ) cost_{a,d}\ge cost_{b,c}(a\le b\le c\le d) costa,dcostb,c(abcd)

此外, 1 1 1的充要条件为 c o s t i , k + c o s t j , h ≤ c o s t i , h + c o s t k , j ( i ≤ j < k ≤ h ) cost_{i,k}+cost_{j,h}\le cost_{i,h}+cost_{k,j}(i\le j<k\le h) costi,k+costj,hcosti,h+costk,j(ij<kh)
这样可以优化 d p i , j = m i n ( d p i , m + d p m + 1 , j ) + c o s t i , j ( i ≤ m < j ) dp_{i,j}=min(dp_{i,m}+dp_{m+1,j})+cost_{i,j}(i\le m< j) dpi,j=min(dpi,m+dpm+1,j)+costi,j(im<j) d p dp dp方程。令 s i , j s_{i,j} si,j为让上述方程取得最小值的 m m m,则有 s i , j − 1 ≤ s i , j ≤ s i + 1 , j s_{i,j-1}\le s_{i,j}\le s_{i+1,j} si,j1si,jsi+1,j O ( n 3 ) O(n^3) O(n3)的方程就能被优化到 O ( n 2 ) O(n^2) O(n2)
同时还能优化 d p j = m i n ( d p i + c o s t i , j ∣ i ∈ [ 0 , j − 1 ] ) dp_{j}=min(dp_i+cost_{i,j}|i\in [0,j-1]) dpj=min(dpi+costi,ji[0,j1])的方程,需要使用单调队列二分, O ( n 2 ) O(n^2) O(n2)的方程就能被优化到 O ( n l o g n ) O(nlogn) O(nlogn)

如果上述条件改为:
c o s t i , j + c o s t i + 1 , j + 1 ≥ c o s t i , j + 1 + c o s t i + 1 , j ( i < j ) cost_{i,j}+cost_{i+1,j+1}\ge cost_{i,j+1}+cost_{i+1,j}(i<j) costi,j+costi+1,j+1costi,j+1+costi+1,j(i<j)

还可以优化 d p i , j = m a x ( d p i , m + d p m + 1 , j ) + c o s t i , j ( i ≤ m < j ) dp_{i,j}=max(dp_{i,m}+dp_{m+1,j})+cost_{i,j}(i\le m< j) dpi,j=max(dpi,m+dpm+1,j)+costi,j(im<j) d p j = m a x ( d p i + c o s t i , j ∣ i ∈ [ 0 , j − 1 ] ) dp_{j}=max(dp_i+cost_{i,j}|i\in [0,j-1]) dpj=max(dpi+costi,ji[0,j1])的方程。

P6246 [IOI2000] 邮局 加强版

题解参考文章
d p dp dp的转移方程为 d p j = m i n ( d p i + c o s t i + 1 , j ∣ i ∈ [ 0 , j − 1 ] ) dp_j=min(dp_i+cost_{i+1,j}|i\in[0,j-1]) dpj=min(dpi+costi+1,ji[0,j1])
c o s t l , r cost_{l,r} costl,r为在第 l l l和第 r r r个邮局之间放一个邮局的最小代价。

cost满足四边形不等式

对于任意 a < a + 1 < b < b + 1 a<a+1<b<b+1 a<a+1<b<b+1,证明 c o s t a , b + c o s t a + 1 , b + 1 ≤ c o s t a , b + 1 + c o s t a + 1 , b cost_{a,b}+cost_{a+1,b+1}\le cost_{a,b+1}+cost_{a+1,b} costa,b+costa+1,b+1costa,b+1+costa+1,b
首先,在 [ l , r ] [l,r] [l,r]之间建立邮局选择 [ l , r ] [l,r] [l,r]之间的中位数最优。
其次,当区间长度为偶数选择中间两个任意一个作为中位数都合法。
从而,区间 [ l + 1 , r ] , [ l , r ] [l+1,r],[l,r] [l+1,r],[l,r]可以取同样的中位数。
那么 [ a + 1 , b + 1 ] [a+1,b+1] [a+1,b+1] [ a , b + 1 ] [a,b+1] [a,b+1]取中位数相同,有 c o s t a , b + 1 − c o s t a + 1 , b + 1 = s m 1 − s a ( 1 ) cost_{a,b+1}-cost_{a+1,b+1}=s_{m_1}-s_a (1) costa,b+1costa+1,b+1=sm1sa(1)
[ a , b ] [a,b] [a,b] [ a + 1 , b ] [a+1,b] [a+1,b]取中位数相同,有 c o s t a , b − c o s t a + 1 , b = s m 2 − s a ( 2 ) cost_{a,b}-cost_{a+1,b}=s_{m_2}-s_a(2) costa,bcosta+1,b=sm2sa(2)
( 1 ) − ( 2 ) (1)-(2) (1)(2) c o s t a , b + 1 + c o s t a + 1 , b − ( c o s t a , b + c o s t a + 1 , b + 1 ) = s m 1 − s m 2 cost_{a,b+1}+cost_{a+1,b}-(cost_{a,b}+cost_{a+1,b+1})=s_{m_1}-s_{m_2} costa,b+1+costa+1,b(costa,b+costa+1,b+1)=sm1sm2
显然 m 1 ≥ m 2 m_1\ge m_2 m1m2,故有 s m 1 ≥ s m 2 → s m 1 − s m 2 ≥ 0 s_{m_1}\ge s_{m_2}\rightarrow s_{m_1}-s_{m_2}\ge 0 sm1sm2sm1sm20。得证 c o s t a , b + 1 + c o s t a + 1 , b ≥ c o s t a , b + c o s t a + 1 , b + 1 cost_{a,b+1}+cost_{a+1,b}\ge cost_{a,b}+cost_{a+1,b+1} costa,b+1+costa+1,bcosta,b+costa+1,b+1

结论

d p j dp_j dpj d p i dp_i dpi转移过来,称 i i i优化 j j j,则在 c o s t i , j cost_{i,j} costi,j满足四边形不等式的情况下, i i i优化的点必为一段连续区间。

反证法

设存在 i ≠ j , a < b < c i\neq j,a<b<c i=j,a<b<c使得 i i i能优化 a , c a,c a,c i i i不能优化 b b b j j j能优化 b b b
则有 ( A )    f i + c o s t i + 1 , a < f j + c o s t j + 1 , a → f i − f j < c o s t j + 1 , a − c o s t i + 1 , a ( B )    f i + c o s t i + 1 , c < f j + c o s t j + 1 , c → f i − f j < c o s t j + 1 , c − c o s t i + 1 , c ( C )    f i + c o s t i + 1 , b > f j + c o s t j + 1 , b → f i − f j > c o s t j + 1 , b − c o s t i + 1 , b (A)\ \ f_i+cost_{i+1,a}<f_j+cost_{j+1,a}\\\rightarrow f_i-f_j<cost_{j+1,a}-cost_{i+1,a}\\(B)\ \ f_i+cost_{i+1,c}<f_j+cost_{j+1,c}\\\rightarrow f_i-f_j<cost_{j+1,c}-cost_{i+1,c}\\(C)\ \ f_i+cost_{i+1,b}>f_j+cost_{j+1,b}\\\rightarrow f_i-f_j>cost_{j+1,b}-cost_{i+1,b} (A)  fi+costi+1,a<fj+costj+1,afifj<costj+1,acosti+1,a(B)  fi+costi+1,c<fj+costj+1,cfifj<costj+1,ccosti+1,c(C)  fi+costi+1,b>fj+costj+1,bfifj>costj+1,bcosti+1,b
整合得 { c o s t j + 1 , b − c o s t i + 1 , b < f i − f j < c o s t j + 1 , c − c o s t i + 1 , c c o s t j + 1 , b − c o s t i + 1 , b < f i − f j < c o s t j + 1 , a − c o s t i + 1 , a \begin{cases}cost_{j+1,b}-cost_{i+1,b}<f_i-f_j<cost_{j+1,c}-cost_{i+1,c}\\cost_{j+1,b}-cost_{i+1,b}<f_i-f_j<cost_{j+1,a}-cost_{i+1,a}\end{cases} {costj+1,bcosti+1,b<fifj<costj+1,ccosti+1,ccostj+1,bcosti+1,b<fifj<costj+1,acosti+1,a
移项得 { c o s t j + 1 , b + c o s t i + 1 , c < c o s t j + 1 , c + c o s t i + 1 , b ( 1 ) c o s t j + 1 , b + c o s t i + 1 , a < c o s t j + 1 , a + c o s t i + 1 , b ( 2 ) \begin{cases}cost_{j+1,b}+cost_{i+1,c}<cost_{j+1,c}+cost_{i+1,b}&(1)\\cost_{j+1,b}+cost_{i+1,a}<cost_{j+1,a}+cost_{i+1,b}&(2)\end{cases} {costj+1,b+costi+1,c<costj+1,c+costi+1,bcostj+1,b+costi+1,a<costj+1,a+costi+1,b(1)(2)
而四边行不等式满足的条件为:
对任意 i ≤ j < k ≤ h i\le j<k\le h ij<kh,有 c o s t i , k + c o s t j , h ≤ c o s t i , h + c o s t j , k cost_{i,k}+cost_{j,h}\le cost_{i,h}+cost_{j,k} costi,k+costj,hcosti,h+costj,k
数轴上画图即交叉小于包含。
则当 i + 1 < j + 1 < b < c i+1<j+1<b<c i+1<j+1<b<c,式子 ( 1 ) (1) (1)不满足。
j + 1 < i + 1 < a < b j+1<i+1<a<b j+1<i+1<a<b,式子 ( 2 ) (2) (2)不满足。
从而结论成立。

从而利用上面的结论,当更新到点 i i i时,可以将 [ 1 , n ] [1,n] [1,n]划分为若干个不同的区间,每个区间被同一个点优化。
如果一个段区间同时能被多个点优化,保留最大的点即可,这样任何时刻 [ 1 , n ] [1,n] [1,n]都能被划分为若干个不重叠的区间。
当更新 d p i dp_i dpi时,在队列里二分,找到能优化 i i i的点,对 i i i进行更新。

随后,求出 i i i能优化的区间,从队列中最后一段区间开始,如果这段区间被 i i i优化更优,将他合并进 i i i优化的区间。否则,对于最后一段区间,二分将其划分成两段,一段合并进 i i i优化的区间,另一段保留。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
using namespace std;
typedef long long ll;
const int N=500005;
int n,m;
ll sum[N],a[N];
ll cost(int l,int r)
{
    int m=l+r>>1;
    return sum[r]-sum[m]-a[m]*(r-m)+a[m]*(m-l+1)-sum[m]+sum[l-1];
}
pair<ll,ll>dp[N];
pair<ll,ll>cal(int i,int j,int k)
{
    if(i>j) return {inf,inf};
    return {dp[i].first+cost(i+1,j)-k,dp[i].second+1};
}
struct node
{
    int x,l,r;
    node(int x=0,int l=0,int r=0):x(x),l(l),r(r){}
};
int top;
node q[N];
pair<ll,ll> sol(int k)
{
    memset(dp,inf,sizeof(dp));
    dp[0]={0,0};
    q[top=1]=node(0,1,n);
    for(int i=1;i<=n;i++)
    {
        int l=1,r=top,p;
        while(l<=r)
        {
            int m=l+r>>1;
            if(q[m].l<=i) p=m,l=m+1;
            else r=m-1;
        }
        dp[i]=cal(q[p].x,i,k);
        p=-1;
        while(top&&cal(i,q[top].l,k)<=cal(q[top].x,q[top].l,k)) p=q[top--].l;
        if(top&&cal(i,q[top].r,k)<=cal(q[top].x,q[top].r,k))
        {
            int l=q[top].l,r=q[top].r;
            while(l<=r)
            {
                int m=l+r>>1;
                if(cal(i,m,k)<=cal(q[top].x,m,k))
                    p=m,r=m-1;
                else l=m+1;
            }
            q[top].r=p-1;
        }
        if(p!=-1)
            q[++top]={i,p,n};
    }
    return dp[n];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    int l=-2000000,r=2000000;
    ll ans;
    while(l<=r)
    {
        int mid=l+r>>1;
        pair<ll,ll>x=sol(mid);
        if(x.second<=m) ans=x.first+1ll*m*mid,l=mid+1;
        else r=mid-1;
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值