POJ3079 K-Anonymous Sequence

题目链接:POJ3079 K-Anonymous Sequence
题目大意:
\(T\) 组数据,有一个长度为 \(N\) 的单调不下降的序列 \(A\) ,每一次操作可以将其中一个数字 \(A_i\) 减1,要求操作后的序列满足每个数字都存在至少 \(k-1\) 个元素和其大小相等,求操作数的最小值。
\(T\leq 20,2\leq N\leq 500000\)\(\forall A_i\in [0,500000]\)

思路:
\(dp[i]\) 为对于前 \(i\) 个数的最小值,\(sum[i]=\sum_{j=1}^i{a_i}\),显然将连续的一段 \(A_i\) 变为同一个值是较优的,首先可以得到 \(O(n^2)\) 的朴素DP:

\[dp[i]=\min_{0\leq j\leq i-k}\{dp[j]+sum[i]-sum[j]-(i-j)*a[j+1]\} \]

考虑优化,将与 \(j\) 无关的项移到外面,简单整理:

\[dp[i]=\min_{0\leq j<i}\{dp[j]-sum[j]+j*a[j+1]-i*a[j+1]\}+sum[i] \]

这里有 \(i*a[j+1]\) 的乘积式,可以斜率优化,将min去掉,移项后可以得到:

\[dp[j]+j*a[j+1]-sum[j]=i*a[j+1]+dp[i]-sum[i] \]

\(dp[j]+j*a[j+1]-sum[j]\) 作为纵坐标, \(a[j+1]\) 作为横坐标,由于后者和斜率 \(i\) 都是单调递增的,可以用单调队列直接维护凸壳,时间复杂度 \(O(n)\)

实现细节:

  • 这个数据范围显然要开long long。
  • 等到 \(dp[i]\) 要进行转移时再将 \(i-k\) 加入单调队列,否则本身要转移的最优状态可能会被后面加入但暂时不转移的状态淘汰掉。
  • \(2k\)\(dp\) 没有转移状态,应直接计算整体成块的答案。

Code:

#include<iostream>
#include<cstdio>
#define N 500100
#define ll long long
using namespace std;
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return s*w;
}
ll a[N],dp[N],sum[N],q[N];
int T,n,k,l,r;
ll dx(ll i,ll j){
    return a[i+1]-a[j+1];
}
ll dy(ll i,ll j){
    return (dp[i]+i*a[i+1]-sum[i])-(dp[j]+j*a[j+1]-sum[j]);
}
int main(){
    T=read();
    while(T--){
        n=read(),k=read();
        for(int i=0;i<n;i++)
            sum[i+1]=sum[i]+(a[i+1]=read());
        l=0,r=-1;
        for(int i=1;i<=n;i++){
            if(i<2*k){dp[i]=dp[i-1]+a[i]-a[1];continue;}
            while(l<r&&dy(q[r],q[r-1])*dx(i-k,q[r])>=dy(i-k,q[r])*dx(q[r],q[r-1]))r--;
            q[++r]=i-k;
            while(l<r&&dy(q[l+1],q[l])<=dx(q[l+1],q[l])*i)l++;
            dp[i]=dp[q[l]]+sum[i]-sum[q[l]]-(i-q[l])*a[q[l]+1];
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}
posted @ 2020-12-01 21:42  Neal_lee  阅读(78)  评论(0编辑  收藏  举报