zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    LeetCode 515. 在每个树行中找最大值(Find Largest Value in Each Tree Row)
    LeetCode 114. 二叉树展开为链表(Flatten Binary Tree to Linked List)
    LeetCode 199. 二叉树的右视图(Binary Tree Right Side View)
    LeetCode 1022. 从根到叶的二进制数之和(Sum of Root To Leaf Binary Numbers)
    LeetCode 897. 递增顺序查找树(Increasing Order Search Tree)
    LeetCode 617. 合并二叉树(Merge Two Binary Trees)
    LeetCode 206. 反转链表(Reverse Linked List) 16
    LeetCode 104. 二叉树的最大深度(Maximum Depth of Binary Tree)
    LeetCode 110. 平衡二叉树(Balanced Binary Tree) 15
    LeetCode 108. 将有序数组转换为二叉搜索树(Convert Sorted Array to Binary Search Tree) 14
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13122006.html
Copyright © 2011-2022 走看看