修剪草坪/选择数字(单调队列优化DP)

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

题意:给定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
",sum-cnt);
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/10322058.html