做到一道题,不会做。
题解说是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;
}