peiwenjun's blog 没有知识的荒原

CF1832F Zombies 题解

题目描述

对于左闭右开区间 \([l,r)\) ,定义区间长度为 \(|[l,r)|=\max(r-l,0)\)

给定 \(n\) 个区间 \([l_i,r_i)\) ,你需要构造 \(k\) 个区间 \([a_j,b_j)\) 和一个长为 \(n\) 的数组 \(p\) ,满足:

  • \(\forall 1\le i\le n\)\(p_i\in[1,k]\)
  • \(\forall 1\le j\le k\)\(|[a_j,b_j)|=m\)\(0\le a_j\lt b_j\le x\)

\(\sum_{i=1}^n|[0,x)\setminus [l_i,r_i)\setminus [a_{p_j},b_{p_j})|\) 的最大值。

数据范围

  • \(1\le k\le n\le 2000,1\le m\le x\le 10^9,0\le l_i\lt r_i\le x\)

时间限制\(\text{4s}\),空间限制\(\text{256MB}\)

分析

第一步容斥大家都会:

\[|[0,x)\setminus[l_i,r_i)\setminus [a_{p_j},b_{p_j})|=x-(r_i-l_i)-m+|[l_i,r_i)\cap [a_{p_j},b_{p_j})|\\ \]

假如已经固定了 \([a_j,b_j)\) ,由于区间长度恒为 \(m\) ,因此对于每个 \([l_i,r_i]\) ,我们一定会选择 \(\frac{a_j+b_j}2\)\(\frac{l_i+r_i}2\) 最近的区间

因此将所有 \([l_i,r_i)\) 按照 \(\frac{l_i+r_i}2\) 升序排序,那么每个集合都是一个连续段。

接下来动态规划, \(f_{i,j}\) 表示用 \(i\) 个集合覆盖前 \(j\) 个区间的最大收益。

转移方程 \(f_{i,j}=\max\limits_{0\le l\lt j}(f_{i-1,l}+g_{l+1,j})\) ,其中 \(g_{l,r}\) 表示将 \([l,r]\) 中的区间划分为一个集合的收益。


先考虑如何求出 \(g\)

注意到合法的 \([a_j,b_j)\)\(\mathcal O(x)\) 个,但是如果 \(a_j,b_j\) 没有顶到左右端点,那么将区间往左或往右滑动,至少有一个不劣。

因此我们只需要考虑满足 \(a_j\in\{l_i\}\or b_j\in\{r_i\}\) 的区间,这样的区间仅有 \(\mathcal O(n)\) 个。

容易发现 \(g\) 满足决策单调性,这是因为在右侧添加区间 \([l_i,r_i)\) 后,我们选择的 \([a_j,b_j)\) 只会往右滑动。

\(\texttt{2D/1D}\) 的优化即可做到 \(\mathcal O(n^2)\) 求出 \(g_{l,r}\) ,注意我们需要对每个 \([a_j,b_j)\) 预处理它与 \([l_i,r_i)\) 交集大小的前缀和,从而做到 \(\mathcal O(1)\) 计算转移代价。


再考虑如何计算 \(f\) ,接下来有三条路可走。

\(\texttt{WQS}\)二分

打表发现 \(f_{i,j}\) 关于 \(i\) 是上凸函数,二分斜率后看成每新开一个集合需要花费 \(mid\) 的代价。

时间复杂度 \(\mathcal O(n^2\log V)\)

#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=2005;
int k,m,n,x,cnt,res,sum;
int c[4005],s[4005][2005];
int g[maxn][maxn],q[maxn][maxn];
pii f[maxn],p[maxn];
inline int jiao(pii a,pii b)
{
    return max(min(a.se,b.se)-max(a.fi,b.fi),0ll);
}
pii calc(int x)
{
    for(int i=1;i<=n;i++) f[i]=mp(0,0);
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++)
            f[i]=max(f[i],mp(f[j].fi+g[j+1][i]-x,f[j].se+1));
    return mp(f[n].se,f[n].fi);
}
signed main()
{
    scanf("%lld%lld%lld%lld",&n,&k,&x,&m);
    for(int i=1,l=0,r=0;i<=n;i++)
    {
        scanf("%lld%lld",&l,&r),sum+=x-(r-l)-m;
        c[++cnt]=l,(r>=m)&&(c[++cnt]=r-m),p[i]=mp(l,r);
    }
    sort(p+1,p+n+1,[&](pii x,pii y){return x.fi+x.se<y.fi+y.se;});
    sort(c+1,c+cnt+1);
    cnt=unique(c+1,c+cnt+1)-c-1;
    for(int j=1;j<=cnt;j++)
        for(int i=1;i<=n;i++)
            s[j][i]=s[j][i-1]+jiao(mp(c[j],c[j]+m),p[i]);
    for(int i=1,j=1;i<=n;i++)
    {
        g[i][i]=min(p[i].se-p[i].fi,m);
        while(j<=cnt&&s[j][i]-s[j][i-1]!=g[i][i]) j++;
        q[i][i]=j;///转移代价相同时,需要找到最小的最优决策点
    }
    for(int len=2;len<=n;len++)
        for(int l=1,r=len;r<=n;l++,r++)
            for(int j=q[l][r-1];j<=q[l+1][r];j++)
            {
                int cur=s[j][r]-s[j][l-1];
                if(g[l][r]<cur) g[l][r]=cur,q[l][r]=j;
            }
    int l=-1,r=2e12;///斜率一定非负,但x=k处斜率为0时仍需进行一次calc(0)来更新答案
    while(r-l>1)
    {
        int mid=(l+r)>>1;
        pii cur=calc(mid);
        if(cur.fi>=k) l=mid,res=cur.se+k*mid;
        else r=mid;
    }
    printf("%lld\n",res+sum);
    return 0;
}
分治优化分层 \(\texttt{1D/1D}\) 型决策单调性

感性理解发现 \(g\) 满足四边形不等式,于是可以决策单调性。

严谨证明:

不妨 \(a\le b\le c\le d\) ,记 \(q_{l,r}\)\(g_{l,r}\) 的最优决策点。

如果 \(q_{a,d}\le q_{b,c}\) ,对于 \(\forall i\in [c+1,d]\) ,第 \(i\) 个区间的中点 \(pos_i\ge mid_{q_{b,c}}\ge mid_{q_{a,d}}\) ,因此将 \([c+1,d]\) 划分到和 \([b,c]\) 同一个集合中一定不劣,即 \(g_{a,d}+g_{b,c}\le g_{a,c}+g_{b,d}\)

\(q_{a,d}\ge q_{b,c}\) 同理。

代码实现应该不需要讲,时间复杂度 \(\mathcal O(n^2+kn\log n)\)

void solve(int i,int l,int r,int L,int R)
{
    if(l>r) return ;
    int mid=(l+r)>>1,pos=0,val=0;
    for(int j=L;j<=min(R,mid);j++)
    {
        int cur=f[i-1][j]+g[j+1][mid];
        if(val<cur) val=cur,pos=j;
    }
    f[i][mid]=val;
    solve(i,l,mid-1,L,pos);
    solve(i,mid+1,r,pos,R);
}
区间 \(\text{DP}\) 优化 \(\texttt{2D/1D}\) 型决策单调性

根据决策单调性的结论, \(q_{i-1,j}\le q_{i,j}\le q_{i,j+1}\)

升序枚举 \(i\) ,倒序枚举 \(j\) ,这样我们在计算 \(f_{i,j}\) 之前,已经求出 \(q_{i-1,j}\)\(q_{i,j+1}\)

时间复杂度 \(\mathcal O(n^2+kn)\)

memset(f,0xcf,sizeof(f));
f[0][0]=0,q[0][0]=0;
for(int i=1;i<=k;i++) q[i][n+1]=n;
for(int i=1;i<=k;i++)
    for(int j=n;j>=1;j--)
        for(int l=q[i-1][j];l<=q[i][j+1];l++)
        {
            int cur=f[i-1][l]+g[l+1][j];
            if(f[i][j]<cur) f[i][j]=cur,q[i][j]=l;
        }

posted on 2023-06-18 21:48  peiwenjun  阅读(7)  评论(0)    收藏  举报

导航