• P2569 [SCOI2010]股票交易


    https://darkbzoj.tk/problem/1855
    https://www.luogu.com.cn/problem/P2569

    单调队列优化,还是看了一眼题解才做出来的/kk

    (1leq BP_ileq AP_ileq 1000,1leq AS_i,BS_ileq ext{MaxP}1)
    (0leq W<Tleq 2000,1leq ext{MaxP}leq20000)


    可以想到设计状态 (f_{i,j}) 表示前 (i) 天,有 (j) 个股票,的最大获利
    分成这么几种情况来进行转移:

    1. 不买不卖
    2. 之前没有买过任何股票,直接买
    3. 之前没有买过任何股票,直接卖
    4. 从之前某个状态继续买
    5. 从之前某个状态继续买

    显然,第三种情况不成立,因为没买过不能卖
    分别讨论其它情况

    第一种,(f_{i,j}=f_{i-1,j})
    第二种,显然 (f_{i,j}=-AP_icdot j)

    三四种稍微复杂
    肯定是从第 (i-w+1) 天转移而来,因为 (i-wsim i-1) 都不符合限制,不行
    但为什么不考虑 (i-w+1) 之前的?因为之前某一天对应的状态会通过第一种不买不卖的情况一路转移到第 (i-w+1)
    看题解前就是因为这一点没想过来才卡着没做出来的
    第三种的转移方程:

    [f_{i,j}=max_{k=j-AS_i}^{j-1} f_{i-w-1,k}-(j-k)cdot AP_i ]

    不难理解,(k) 的限制条件是为了符合买入数量的显示,(-(j-k)cdot AP_i) 就是买入要花的钱
    同理,第四种也类似:

    [f_{i,j}=max_{k=j+1}^{j+BS_i} f_{i-w-1,k}+(k-j)cdot BP_i ]


    但发现这样转移复杂度不行,还要优化一下
    因为是取 (max),而且还是一个不断整体移动且长度不变的区间(比如第三种情况是 (j-AS_isim j-1)),所以想到单调队列,其实是用标签搜进来的
    给式子变形,以第三种为例

    [f_{i,j}=(max_{k=j-AS_i}^{j-1} f_{i-w-1,k}+kcdot AP_i)-jcdot AP_i ]

    就是用一下乘法分配然后把 (-jcdot AP_i) 提出去
    这样,每次把 (f_{i-w-1,k}+kcdot AP_i) 放入队列,然后更新的时候就是用队列的最大值减去 (-jcdot AP_i) 来取 (max) 更新

    第四种的式子:

    [f_{i,j}=(max_{k=j+1}^{j+BS_i} f_{i-w-1,k}+kcdot BP_i)-jcdot BP_i ]

    代码:

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<map>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    int n,maxp,w;
    int f[2005][2005];
    int tail,head,num[2005],ind[2005];
    int main(){
    	n=read();maxp=read();w=read();
    	std::memset(f,128,sizeof f);
    	for(reg int ap,bp,as,bs,i=1;i<=n;i++){
    		ap=read();bp=read();as=read();bs=read();
    		for(reg int j=0;j<=as;j++) f[i][j]=-ap*j;//直接买 
    		for(reg int j=0;j<=maxp;j++) f[i][j]=std::max(f[i][j],f[i-1][j]);//不买不卖
    		if(i<=w) continue;
    		//从原来某状态继续买
    		tail=head=0;num[0]=f[i-w-1][0];ind[0]=0;
    		for(reg int tmp,j=1;j<=maxp;j++){
    			if(tail<=head) f[i][j]=std::max(f[i][j],num[tail]-j*ap);
    			tmp=f[i-w-1][j]+j*ap;
    			while(tail<=head&&num[head]<=tmp) head--;
    			num[++head]=tmp;ind[head]=j;
    			while(tail<=head&&ind[tail]<j+1-as) tail++;
    		}
    		//从原来的某状态继续卖
    		//因为这个被转的区间是 [j+1,j+bs],所以倒叙循环 
    		tail=head=0;num[0]=f[i-w-1][maxp]+bp*maxp;ind[0]=maxp;
    		for(reg int tmp,j=maxp-1;~j;j--){
    			if(tail<=head) f[i][j]=std::max(f[i][j],num[tail]-j*bp);
    			tmp=f[i-w-1][j]+j*bp;
    			while(tail<=head&&num[head]<=tmp) head--;
    			num[++head]=tmp;ind[head]=j;
    			while(tail<=head&&ind[tail]>j-1+bs) tail++;
    		}
    	}
    	int ans=0;
    	for(reg int i=0;i<=maxp;i++) ans=std::max(ans,f[n][i]);
    	std::printf("%d",ans);
    	return 0;
    }
    
  • 相关阅读:
    MySQL —— 程序连接时的驱动名称和URL
    这该称作什么效应?
    Java 基础 面向对象之关键字内部类代码块修饰符
    Java 基础 面向对象之构造方法和关键字
    Java 基础 接口和多态
    Java 基础 面向对象和抽象类
    Java 基础 引用数据类型 ArrayList集合
    Java 基础 方法
    Java 基础 引用数据类型 和 流程控制
    Mongodb 基础 复制集原理和搭建
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13122006.html
Copyright © 2020-2023  润新知