NOI2007货币兑换CASH 斜率DP

来源:互联网 发布:网站源码加密破解 编辑:程序博客网 时间:2024/04/29 05:43

做到一道题,不会做。

题解说是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;   autodouble 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;//在凸壳内部,直接returnelse 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 0
原创粉丝点击