NOI2007货币兑换CASH 斜率DP

3 篇文章 0 订阅
2 篇文章 0 订阅

做到一道题,不会做。

题解说是FFT+ CDQ分治。

学会了FFT,发现不会CDQ分治。

去看CDQ的论文,发现用了CASH这道题……

然后貌似有斜率DP的东西,就顺便学了斜率DP,写了之前HDU的一道题,继续写这道题……


各种题解各种SPLAY,我一想,这道题用map就行了啊……然后写了200多行,还超级慢……


最囧的问题,在我和朋友的电脑上,用NOI的数据AC,在vijos上,WA6,后面T了。


后来发现因为我偷懒,反复调用map超级慢,但是不想改了。。就这样吧,算是理解斜率DP的各种囧东西了。 以后再有空补splay写法吧(自我安慰,不会补了……)




至于题解,网上到处都有……我再重复一次吧,如果有人需要的话。



f[i]表示第i天,所能获得的最大 金钱数量。

如果知道了f[i],那么我们可以用x[i]表示第i天的钱,全部买A卷,能购买的数量,y[i]则为B卷数量。

至于x[i], y[i]的求解,就不再赘述……(列方程表示一下就能求出来啦~)


然后f[i]可以写成类似于 f[i] = max{f[i-1], max{x[j]*a[i]+y[j]*b[i]}}的形式


不考虑狮子中f[i-1] 的部分,强行变换一下


f[i]=x[j]*a[i]+y[j]*b[i]


式子变形后为

f[i]/b[i] - x[j]*(a[i]/b[i])=y[j]


a[i],b[i]为常数,也就是第i天A,B卷的价格。

这个式子形如y=kx+b


求i的时候,[1,i-1]的f值都已经知道了,所以可以弄出很多x[j],y[j]的点对。 相当于我在一张图上,有很多(x,y)的坐标,我有一个斜率k,要找一个点,去放上这个斜率k,使得方程中的b最大。 

y=kx+b是截距式,并且k恒为负数(a[],b[]为正数,式子中有符号。  同时,题目给定的rate比例是[0,100],保证了b为非0,所以斜率永远存在~)


现在问题就转变为,根据前面的乱七八糟的x,y的点坐标,找一个最优的点坐标,来匹配上k,求出f[i]的值啦



然后显然(这个显然可以自己画图……当然别人的文章有图啦~我比较懒) 解一定在上凸壳上,所以首先我们要维护一个上凸壳。


对于每次插入新的点,可以看这个点在凸壳内部(下方),还是上方。因为凸壳的点 x坐标是有序的,所以可以用map维护x坐标,和x坐标里保存的y坐标信息,以及这个点和左边的点的斜率,和右边点的斜率。


比如说,第三个点,和第二个点斜率, 以及和第四个点的斜率。维护这些信息会方便一些~


【第一个点和左边的点斜率为0, 最后一个点和右边的点斜率为负无穷】 这个很重要,问我为啥知道的。。我举例子弄的……没法证明。


然后再用一个map来维护斜率,映射斜率对应的x坐标即可。


然后维护这两个map,我的代码太臃肿,并且因为偷懒,反复调用了很多东西。而且确实,我的map水平不堪入目啊……


然后细节很多。。。

情况1: 插入的点,其x坐标是已插入点最大/最小的。

情况2:插入的点,和之前插入的点,x坐标相同(若新点y坐标更小,直接抛弃,否则需要删除曾经插入的点,来插入新的点)

还有一些情况,在写map的时候回自己感觉到。。。如果写 splay,就不会因为出现不好判断(--map.begin())之类的情况的问题啦~ 其实写其他平衡树也行。


上代码:很慢,最慢的3秒多。


#include <cstdio>
#include <algorithm>
#include <ctime>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <map>
using namespace std;


typedef long long LL;

int n, s;
const double inf = -1000000000000;
const int maxn = 100000 +100;
double a[maxn], b[maxn], r[maxn];
double f[maxn];


struct node
{
	double x, y;//这个节点的x,y坐标
	double lk, rk;//和左边节点的斜率,和右边节点的斜率
	//如果左边没有节点,斜率为0。  右边没有节点,斜率为inf (负无穷)
	node(double _x, double _y, double _lk, double _rk):x(_x),y(_y),lk(_lk),rk(_rk){}
	node(){}
};
typedef map<double, node>::iterator mit;

map<double, node> mp; //double表示那个点坐标再x位置,node是那个点的各种信息
map<double, node> xie; //表示first为斜率, second是x坐标是x'的,和比x小的那个坐标的斜率。

double xielv(node A, node B)
{
	return (A.y-B.y) / (A.x-B.x);
}


double getX(int k)//已知道f[k]的情况下, 求出x[k]
{
	return (r[k] * f[k]) / (r[k] * a[k] + b[k]);
}

double getY(int k)
{
	return f[k] / (r[k] * a[k] + b[k]);
}

void init()
{
	mp.clear();
	xie.clear();
	f[0] = s;
	for (int i = 1; i <= n; ++ i)
		scanf("%lf%lf%lf", &a[i], &b[i], &r[i]);
	f[1] = s;
	double x = getX(1);
	double y = getY(1);
	mp[x] = node(x, y, 0, inf);
	xie[0] = node(x, y, 0, inf);
}

double get_f(int p)//条件充分,计算f[p]值
{
	double k = -a[p] /b[p];//显然就是-r[p],保证斜率存在了
	//auto it = xie.find(k);
	mit it = xie.find(k);
	//存在一条斜率,和k相同的。显然斜率不可能大于0了,所以左边界问题也同时解决了
	//找比k大的一个,一定存在,毕竟0是最大的数字,其他数字都是负数
	if (it == xie.end())    it = xie.upper_bound(k);
	//auto info = it->second;//info是一大堆信息,包含了一个坐标,斜率为k的直线,就会在这个坐标上
	node info = it->second;//info是一大堆信息,包含了一个坐标,斜率为k的直线,就会在这个坐标上
	f[p] = info.y * b[p] + info.x * a[p];
	if (f[p] < f[p-1])   f[p] = f[p - 1];
}

void del(double x)
{
	//auto it = mp.find(x); //找到x坐标,和这个点的一些信息,显然it一定存在
	mit it = mp.find(x);
	double k = (it -> second).lk;//斜率,并且斜率也一定存在
	mp.erase(it);
	xie.erase(k);
}

void ins(double x, double y)
{
	//开始插入x了
	mp[x] = node(x,y,666,666);//666 666是我随意插入的,并不重要也没有什么卵用
	//auto it = mp.find(x);
	//auto l_it = it, r_it = it;    
	mit  it=mp.find(x), l_it = it, r_it = it;//l_it,r_it分别为比it->first,小的,和大的的迭代器。 

	-- l_it;
	++ r_it;

	if (it == mp.begin())   goto dont_delte_left;//如果it一开始就是整棵树中最小的元素

	//删除所有左边的情况
	while ((l_it != mp.begin()) && xielv(it->second, l_it -> second) >= (l_it -> second).lk)
	{
		//auto t = l_it -> first;
		double t = l_it -> first;
		l_it--;
		del(t);//删了
	}

	if (l_it == mp.begin() && xielv(it->second, l_it -> second) >= (l_it -> second).lk)
	{
		//auto t =l_it -> first;
		double t =l_it -> first;
		l_it--;
		del(t);
	}

dont_delte_left:;


		if (it == mp.begin())   //自己已经是最左边界的情况
		{
			(it -> second).lk = 0;
			xie[0] = it -> second;
		}
		else
		{
			(l_it-> second).rk = it -> second.lk = xielv(it -> second, l_it -> second);
			xie[(it-> second).lk] = it -> second;
			((++xie.find((it-> second).lk)) -> second).rk = (it -> second).lk;
		}

		while (r_it != mp.end() && xielv(it-> second, r_it-> second) <= (r_it -> second).rk)
		{
			//auto t = (r_it -> second).x;   auto
			double t = (r_it -> second).x;
			r_it ++;
			del(t);
		}
		if (it == --mp.end())
		{
			(it -> second).rk = inf;
			xie.begin() -> second.rk = inf;
		}else
		{
			double p = r_it->second.lk;
			(r_it -> second).lk = (it -> second).rk = xielv(it-> second, r_it -> second);
			node tmp = r_it -> second;
			xie.erase(p);
			xie[it -> second.rk] = tmp;
		}
		xie[it->second.lk] = it-> second;
}




void doit()
{
	for (int i = 2; i <= n; ++ i)
	{
		/*更新f[i]的值*/
		get_f(i);
		//auto x = getX(i);
		//auto y = getY(i);
		double auto x = getX(i);
		double auto y = getY(i);
		mit it = mp.find(x);
		//判断是否在凸壳内
		if (it != mp.end())
		{
			double yy = (it -> second).y;
			if (yy >= y) continue;//在凸壳内部,直接return
			else del(x);    //否则一定是在凸壳外部,要先删去x原来的情况 ,然后插入(x,y)
			ins(x, y);
			continue;
		}

		mp[100000000] = node(0, 100000000,0,0);
		mp[inf] = node(100000000,0,0,0);

		it = mp.upper_bound(x);
		double kr = xielv(it -> second, node(x,y,0,0));
		double kl = xielv((--it) -> second, node(x, y, 0, 0));

		mp.erase(100000000);
		mp.erase(inf);
		if (kl < kr) //在凸壳内
		{
			continue;
		}
		ins(x, y);
	}
	printf("%.3f\n", f[n]);

}

int main()
{
	while (~scanf("%d%d", &n, &s))
	{
	//	scanf("%d%d", &n, &s);
		init();
		doit();
	}
	return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值