「一本通」单调队列优化dp学习笔记

总结:

题目一般要求由前面的一个状态得出当前的最优状态,满足dp,但如果暴力查找前一个决策,复杂度显然不可以接受。这时候可以用一个能从两端删除但只能从一段添加的单调队列及时把不可能的决策排除掉,然后再把当前的决策插进去,保持队列中的单调性。然后就乱搞。(表示进阶上的题的证明好强满足等式右边的单调性后还要加set优化f数组)


loj#10175. 「一本通 5.5 例 1」滑动窗口

https://loj.ac/problem/10175
代码略(ju)丑

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	int x,c;
	node()
	{
		c=-999999999;
		x=0;
	}
}lmn[1100000],lmx[110000];
int mn[1100000],mx[1100000];
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	int c,hmn=1,tmn=0,hmx=1,tmx=1;
	scanf("%d",&c);
	lmn[++tmn].x=1;lmn[tmn].c=c;lmx[++tmx].x=1;lmx[tmx].c=c;
	for (int i=2;i<=n;i++)
	{
		scanf("%d",&c);
		while (hmn<=tmn&&lmn[tmn].c>c) tmn--;
		lmn[++tmn].x=i;lmn[tmn].c=c;
		while (hmn<=tmn&&i-lmn[hmn].x+1>k) hmn++;
		
		while(hmx<=tmx&&lmx[tmx].c<c) tmx--;
		lmx[++tmx].x=i;lmx[tmx].c=c;
		while (hmx<=tmx&&i-lmx[hmx].x+1>k) hmx++;
		
		if (i>=k)
		{
			mn[i-k+1]=lmn[hmn].c;
			mx[i-k+1]=lmx[hmx].c;
			
		}
	}
	for (int i=1;i<=n-k+1;i++) printf("%d ",mn[i]);
	printf("\n");
	for (int i=1;i<=n-k+1;i++) printf("%d ",mx[i]);
	printf("\n");
	return 0;
}

loj#10176. 「一本通 5.5 例 2」最大连续和

https://loj.ac/problem/10176
将求区间和转换为前缀和相减 (s[i]-s[j],j<i)
对于一个s[i],s[j]越小区间和越大
所以维护一个单调上升的序列(维护队头是最小最优决策),同时统计答案

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	int x,c;
}list[210000];
int s[210000],a[210000];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s[i]=s[i-1]+a[i];
	}
	//维护一个严格单调上升序列 
	int head=0,tail=0,ans=-2147483647;
	list[0].x=0;list[0].c=0;
	for (int i=1;i<=n;i++)
	{
		while (head<=tail&&i-list[head].x>m) head++; //选list[head].x+1 ~ i 
		ans=max(s[i]-list[head].c,ans);
		while (head<=tail&&list[tail].c>s[i]) tail--;
		list[++tail].c=s[i]; list[tail].x=i;
	}
	printf("%d\n",ans);
	return 0;
}
loj#10177. 「一本通 5.5 例 3」修剪草坪

https://loj.ac/problem/10177
书上写的解法是很dp的做法,f[i][0],f[i][1]代表第i只牛选或不选的最大效率,用单调队列优化决策,维护一个单调下降的序列
我的的是用总和减去不选的奶牛的效率……
将问题转化为每k+1个数字中必须选择一个,使选出的总和最小

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	int x;long long c; //x:最后一只的位置 
}list[110000];
int a[110000];
long long s[110000],f[110000];
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s[i]=s[i-1]+a[i];
	}
	//维护一个严格单调递增序列 
	int head=0,tail=0;
	list[0].x=0;list[0].c=0;
	for (int i=1;i<=n;i++)
	{
		while (head<=tail&&i-list[head].x>k+1) head++;
		if (i<=k+1) f[i]=a[i]; else f[i]=list[head].c+a[i];
		while (head<=tail&&list[tail].c>f[i]) tail--;
		list[++tail].c=f[i]; list[tail].x=i;
	}
	/*long long ans=2147483647;
	for (int i=1;i<=n;i++) ans=min(ans,f[i]);*/
	while (head<=tail&&n-list[head].x>k) head++;
	printf("%lld\n",s[n]-list[head].c);
	return 0;
}

loj#10178. 「一本通 5.5 例 4」旅行问题

https://loj.ac/problem/10178
用油-路程,即可判断这一段路能否走通,求前缀和后即可判断起点到i能否走通
判断是否能走通,即判断i~i+n是否有负数
直接判复杂度为 N 2 N^2 N2,显然是无法接受的
考虑将问题转化为例1,找到i~i+n里面的最小值,判断最小值是否小于零
至于顺逆时针……还是建议画个图感受一下位置和数字的关系吧2333

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
struct node
{
	LL c;int x;
}lista[210000],listb[210000];
LL disa[210000],disb[210000],gasa[210000],gasb[210000],a[210000],b[210000];
bool ans[210000];
int main()
{
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%d%d",&gasa[i],&disa[i]); 
		disa[n+i]=disa[i];
		//disb[n-i+1]=disa[i]; disb[n-i+1+n]=disa[i];
		gasa[n+i]=gasa[i];
		//gasb[n-i+1]=gasa[i]; gasb[n-i+1+n]=gasa[i];
	}
	for (int i=1;i<=2*n;i++) 
	{
		a[i]=gasa[i]-disa[i]+a[i-1];
		b[i]=gasa[2*n-i+1]-disa[2*n-i]+b[i-1];
	}
	/*
	维护一个单调上升序列  
	找到i~i+n-1内最小值  最小值-自己>=0 一定区间内没有负数 
	注意顺逆时针的对应关系 */
	int heada=1,taila=0,headb=1,tailb=0;
	lista[1].x=0; lista[1].c=0;
	listb[1].x=0; listb[1].c=0;
	for (int i=1;i<=2*n;i++)
	{
		while (heada<=taila&&i-lista[heada].x>n) heada++;
		while (heada<=taila&&a[i]<=lista[taila].c) taila--;
		lista[++taila].c=a[i]; lista[taila].x=i;
		while (headb<=tailb&&i-listb[headb].x>n) headb++;
		while (headb<=tailb&&b[i]<=listb[tailb].c) tailb--;
		listb[++tailb].c=b[i]; listb[tailb].x=i;
		if (i>n)
		{
			if (lista[heada].c-a[i-n-1]>=0) ans[i-n]=1;
			if (listb[headb].c-b[i-n-1]>=0) ans[n-(i-n)+1]=1;
		}
	} 
	for (int i=1;i<=n;i++) if (ans[i]==1) printf("TAK\n");	else printf("NIE\n");
	return 0;
}

#10179. 「一本通 5.5 例 5」Banknotes

https://loj.ac/problem/10179

思路&题解:

https://blog.csdn.net/WWWengine/article/details/82187471
完全不知道书上在讲什么.jpg
bzoj居然把POI的题解开了权限,感动

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	int c,x;
}list[21000];
int b[21000],c[21000],f[210000]; 
int main()
{
	int n,k;
	scanf("%d",&n);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值