修剪草坪/选择数字

双倍经验!!! 双倍快乐!!!

题意:给定n个非负整数\(a[1]...a[n]\).现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择.你的任务是使得选出的数字的和最大.

分析:正难则反,设\(f[i]\)表示表示不选第i头牛的最小代价,结果会发现:\(f[i]=min(f[j])+a[i]\),于是可以单调队列,\(O(n)\)优美地过了本题.


ll n,k,sum,cnt=1e16;
ll val[100005];
ll q1[100005],q2[100005],f[100005];
int main(){
    n=read();k=read();
    for(ll i=1;i<=n;i++){
        val[i]=read();
        sum+=val[i];//记录下所有数的和
    }
    int head=0,tail=0;//设置队列头,尾指针
    for(int i=1;i<=n;i++){
        f[i]=q1[head]+val[i];
//当前的第i个数不选,表示出最小代价f[i]
//q1[]是从小到大的单调队列,记录的是不选的代价
        while(head<=tail&&q1[tail]>f[i])
            tail--;
//要把当前不选的代价塞进单调队列q1中
        while(head<=tail&&q2[head]<i-k)
            head++;
//q2[]也是从小到大的单调队列,记录的是数的编号(下标)
//因为头尾指针都是head,tail,所以两个队列是并行的.
//换句话说,两个队列中的值一一对应.
        q1[++tail]=f[i];
        q2[tail]=i;
//把当前代价和不选的数的编号分别入队.
    }
    for(int i=n-k;i<=n;i++)
        cnt=min(cnt,f[i]);
//要拿总和减去最小代价,当然是找最小值啦!
    printf("%lld\n",sum-cnt);
    return 0;
}

总结:又又又一次体现出了单调队列优化DP的时间复杂度.

猜你喜欢

转载自www.cnblogs.com/PPXppx/p/10322058.html