CCF CSP 集合竞价 (2014-12)

问题描述
  某股票交易所请你编写一个程序,根据开盘前客户提交的订单来确定某特定股票的开盘价和开盘成交量。
  该程序的输入由很多行构成,每一行为一条记录,记录可能有以下几种:
  1. buy p s 表示一个购买股票的买单,每手出价为p,购买股数为s。
  2. sell p s 表示一个出售股票的卖单,每手出价为p,出售股数为s。
  3. cancel i表示撤销第i行的记录。
  如果开盘价为p 0,则系统可以将所有出价至少为p 0的买单和所有出价至多为p 0的卖单进行匹配。因此,此时的开盘成交量为出价至少为p 0的买单的总股数和所有出价至多为p 0的卖单的总股数之间的较小值。
  你的程序需要确定一个开盘价,使得开盘成交量尽可能地大。如果有多个符合条件的开盘价,你的程序应当输出最高的那一个。
输入格式
  输入数据有任意多行,每一行是一条记录。保证输入合法。股数为不超过10 8的正整数,出价为精确到恰好小数点后两位的正实数,且不超过10000.00。
输出格式
  你需要输出一行,包含两个数,以一个空格分隔。第一个数是开盘价,第二个是此开盘价下的成交量。开盘价需要精确到小数点后恰好两位。
样例输入
buy 9.25 100
buy 8.88 175
sell 9.00 1000
buy 9.00 400
sell 8.92 400
cancel 1
buy 100.00 50
样例输出
9.00 450
评测用例规模与约定
  对于100%的数据,输入的行数不超过5000。

————————————————————————————————————

题目分析

严重吐槽一下,这题cancel能不能取消另一行的cancel存在很大争议。看到有博主说可以,按这个思路改怎么也取消不了。改为不能取消就100分了。CCF这种问题毫无意义。

这题拿分简单,关键是要弄懂隐含的规则,否则拿满分不易。隐含的规则就是最终输出的开盘价一定是输入的p中。为什么呢,将测试案例按p从小到大排列开,如下图所示。图示中各价格用序号标识好,方便说明。


(下述最优解意思是可以取到最大开盘价的解)

假设(8.92,9.00)区间(注意是开区间)内某一点p是最优价格,那么大于或等于这个价格的buy点有3、5,小于等于它的sell点有2,那么不管p在区间(8.92,9.00)内怎么浮动,都不影响大于它的sell点和小于它的buy点的选择,都是点2和点3、5。题目要求取最大值,所以在假设在(2,3)区间存在最优价格的情况下,取值可以无限逼近与9.00。

当价格9.00存在sell点时,值取到9.00显然不会改变原有的buy、sell点的选择。

当9.00存在sell点时,取9.00将会影响buy、sell点的选择。在趋于9.00时,设sell的总股数为a,buy的总股数为b。取到9.00时.设sell的总股数为a+c,buy的总股数为b,很容易明白min(a,b) <= min(a+c,b),即在9.00存在sell点时,得到的结果总会优于趋近于9.00却没有取9.00上的sell点的情况。推广开来,我们可以知道,假设两个点的区间(a,b]上存在最优解,那么b一定也是最优解,且b是最大的,所以b是答案。所以答案一定是存在于已知的点上 。

有人可能还会有疑问这,道题趋近于9和取就是两种截然不同的,取就的话sell就可以取点4,是不是说明有可能存在某种可能,取了点4结果不是最优的,而不取点9.00却趋近于点9.00(比如价格为8.99)价格是最优的?我们上面的证明得出以下结论:

S:两个已知点a,b构成的区间(a,b)存在最优解  B:b是最优解

在已知条件S的情况下可以推出结论B。即S -> B

但是对于S的否命题 S两个已知点a,b构成的区间(a,b)不存在最优解。是不能得出b是最优解的。即S -> B是错误的。

所以区间(a,b)存不存在最优解和b是最优解没有直接关系,但是(a,b)存在最优解b就一定是最优解。

————————————————————————————————————————————————

算法解释

既然已知开盘价一定是输入的价格中的一个,那么将所有价格从小到大排开,循环依次检测每个价格,比较出最小的值就行。要注意的是每次判断一个价格选择时的成交价,我们不必从头到尾计算一次该价格下的成交价,这样太费时间,对于本体测试数据有以下优化算法:

  1. sum1用于计算buy买单总股数,sum2用于计算sell卖单总股数。初始化sum1为所有的buy点的和。测试数据中为625,sum1=625。sum2=0。flag=0
  2. 首先检测最小的价格8.88时,该点为buy点,标记flag为1。sum1,sum2不变。取sum1和sum2中的最小值。
  3. 到第2个点8.92时,由于flag==1,sum1减去上一个buy的值,并把flag置0,即sum1 = sum1- 175。由于该点是sell点,sum2 = sum2 + 400。比较sum1和sum2。
  4. 到到9.00时,检测到该点既有sell点也有buy点,则flag=1,sum2=sum2+1000。比较sum1,sum2。
  5. 后面重复上面步骤到经过所有点。注意,如果存在多个buy点价格一样,或者多个sell点价格一样,一定要合并相同点,即把所有的股数累加到一个点上并删除其他相同的点。否则会重复计算。
保证每个价格(不是每个点)只检测一遍,代码中我用inst表示按价格从小到大排列的所有可能选择的价格。in1所有合并同一价格的buy点,in2表示所有合并同一价格的sell点。
代码如下:
	#include <iostream>
	#include <vector>
	#include <iomanip>
	#include <algorithm>
	using namespace std;
	struct ins
	{
		int count;
		int flag;
		double price;
		long long num;
		int use_flag;
	};
	vector <struct ins> inst,in1,in2;
	bool cmp(struct ins a,struct ins b)
	{
		if(a.price < b.price) 
		{
		    return true;	
		} 
		else   
		{
		    return false;	
		}
	}
	
	int main()
	{
		long long temp,num1=0,num2=0,flag=0;
		string s0;
		double p,PP;
		int i,j;
		long long n,sum1,sum2,SSUM=0;
		struct ins po;
		while(cin >> s0)
		{
			if(s0=="buy") 
			{
				cin >> p >> n;
				po.flag = 0;
				po.price = p;
				po.num = n;
				po.use_flag = 0; 
				inst.push_back(po);
			}
			else if(s0=="sell")
			{
				cin >> p >> n;
				po.flag = 1;
				po.price = p;
				po.num = n;
				po.use_flag = 0;
			    inst.push_back(po);
			}
			else if(s0=="cancel")
			{
				cin >> n;
				po.flag = 2;
	            po.num = n;
	            po.use_flag = 0;
	            inst.push_back(po);
			}
		}
		//标记需要删除 
		for(i=0;i<inst.size();i++)
		{
		//本来设置的是cancel可以取消另一行的cancel,结果只有50分,改为不允许取消就有100分了 
			if(inst[i].flag == 2 )
			{
				j = inst[i].num;
				inst[j-1].use_flag = 1; 
			}
		}
		
		
		for(i=0;i<inst.size();)
		{
			if(	inst[i].flag == 2 || inst[i].use_flag == 1)
			{
				//删除cancel和所有被cancel删除的 
				inst.erase(inst.begin()+i);
			}else{
				if(inst[i].flag == 0) //将buy插入in1 
				{
					in1.push_back(inst[i]);
				}else{                //将sell插入in2 
					in2.push_back(inst[i]);
				}
				i++;
			}
		}
		//从小到大排序 
		sort(in1.begin(),in1.end(),cmp);
		sort(in2.begin(),in2.end(),cmp);
		sort(inst.begin(),inst.end(),cmp);
		
		//删除相同价格的 
		for(i=0;i<inst.size()-1;)
		{
			if(inst[i].price == inst[i+1].price)
			{
				inst.erase(inst.begin()+i);
			}else
			{
				i++;
			}
		}
		
		sum1 = 0;
		sum2 = 0;
		 //合并相同价格的buy点 
		for(i=0;i<in1.size()-1;)
		{
			if(in1[i].price == in1[i+1].price)
			{
				in1[i].num += in1[i+1].num;
				in1.erase(in1.begin()+i+1);
			}else{
				i++;
			}
		}
		 //合并相同价格的sell点 
		for(i=0;i<in2.size()-1;)
		{
			if(in2[i].price == in2[i+1].price)
			{
				in2[i].num += in2[i+1].num;
				in2.erase(in2.begin()+i+1);
			}else{
				i++;
			}
		}
		//统计所有buy点的和 
		for(i=0;i<in1.size();i++)
		{
			sum1 += in1[i].num;
		}
		
		for(i=0;i<inst.size();i++)
		{
			p = inst[i].price;
			if(flag == 1)
			{
				flag = 0;
				sum1 = sum1 - in1[num1-1].num;
			}
			if( in1[num1].price == p )
			{ 
				num1++;
			    flag=1; 
			}
			if(in2[num2].price == p)
			{
				
	     		sum2 = sum2 + in2[num2++].num;	
			}
			
			if(sum1 > sum2)
			{
				temp =  sum2;
				
			}else
			{
				temp = sum1;
			}
			if(temp >= SSUM)
		    {
			    PP=p;
			    SSUM=temp;
		    }
		}
		
		cout << setiosflags(ios::fixed) << setprecision(2) << PP << " " << SSUM;
	
		return 0;
	}


  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值