@rebirth1120
2019-08-07T07:16:33.000000Z
字数 3621
阅读 956
NOI 解题报告 dp
有 两种金券,它们每天的价值都不同,在第 天时分别为 ,你可以用手中的金券换取相应价值的钱,或用钱兑换相同价值的金券,但兑换到的两种金券的数量之比是一个定值,这个定值在第 天为 。
现给出 天中两种金券的价值, ,以及你初始时拥有的资金 ,求 天后你最多能有多少钱。
注:一定存在一种最优方案,满足:每次兑换金券花光所有的钱,每次换钱时花掉所有的金券。
一眼dp题(大佬好像都是这样说的)。
先设个状态吧,
设 表示在第 天卖掉所有的金券能获得的最多钱数,那我们只需要决定上一次买金券是在什么时候,转移方程如下:
假设我们上一次是在第 天买的金券,而上一次卖掉金券是在第 天(),那么我们在第 天拥有的资金就是 ,可以得出式子:
辣眼睛
一个 算法就完成了,看下数据范围
对于 的测试数据,满足 .....
手动再见
没办法,考虑优化吧。
再看眼式子
打开题解看一眼,平衡树,cdq,关上。
cdq没有思路,还是用平衡树吧,虽然写起来麻烦点(为了写这道题我又去敲了遍平衡树的板子,又调了一个小时……)
那现在思路就很明了了(可能吧),用平衡树维护一个上凸包,将凸包上的点按横坐标排序,然后每次找地方插入(或不插入),然后删除没用的点就行了,并在每一个点存下它与凸包上前一个点连接形成的直线的斜率 ,更新状态时找到凸包上 比当前直线大的第一个点进行转移就行了(建议自己画个图理解一下)。
这道题貌似到这里就全部解完了,但实际上还有很多细节需要考虑,包括在插入、删除节点是要更新它的前驱和后缀的信息,以及花式卡精度……(数据里竟然出现了两个点的横坐标小数点后十几位都相同的情况……)。
最后这道题做下来前前后后花了可能有两天……
#include<iostream>#include<cstdlib>#include<cstdio>#define db doubleusing namespace std;const int N=100000+7;int n,q[N],rt,cnt;db f[N],a[N],b[N],r[N],X[N],Y[N],K[N],maxn;int lc[N],rc[N],p[N],pre[N],nxt[N];db tk[N];db read(){char c=getchar(); db f=1,x=0;while(c<'0'||c>'9'){ f=c=='-'?-1:1;c=getchar(); }while(c>='0'&&c<='9'){ x=x*10+c-'0';c=getchar(); }if(c!='.') return f*x;c=getchar(); db y=1;while(c>='0'&&c<='9'){ y/=10;x+=(c-'0')*y;c=getchar(); }return f*x;}void zig(int &u){int ls=lc[u];lc[u]=rc[ls];rc[ls]=u;u=ls;}void zag(int &u){int rs=rc[u];rc[u]=lc[rs];lc[rs]=u;u=rs;}int g_pre(int k){int u=rt,ans=0;while(u){if(X[u]<X[k]){ ans=u; u=rc[u]; }else u=lc[u];}return ans;}int g_nxt(int k){int u=rt,ans=0;while(u){if(X[u]>X[k]){ ans=u; u=lc[u]; }else u=rc[u];}return ans;}db g_k(int a,int b){if(!a||!b) return 0;return (Y[a]-Y[b])/(X[a]-X[b]);}bool insert(int &u,int k){if(!u||X[u]==X[k]){if(X[u]==X[k]&&Y[k]<=Y[u]) return 0;u=k;pre[u]=g_pre(u);nxt[u]=g_nxt(u);if(pre[u]) nxt[pre[u]]=u;if(nxt[u]) pre[nxt[u]]=u;tk[u]=g_k(u,pre[u]);if(nxt[u]) tk[nxt[u]]=g_k(u,nxt[u]);p[u]=rand();return 1;}if(X[u]>X[k]){bool f=insert(lc[u],k);if(p[lc[u]]<p[u]) zig(u);return f;}else{bool f=insert(rc[u],k);if(p[rc[u]]<p[u]) zag(u);return f;}}void del(int &u,int k){if(u==k){if(!lc[u]||!rc[u]){ u=lc[u]+rc[u];return; }else if(p[lc[u]]<p[rc[u]]){ zig(u);del(rc[u],k); }else{ zag(u);del(lc[u],k); }}else if(X[u]>X[k]) del(lc[u],k);else del(rc[u],k);}void add(int k){X[k]=r[k]*maxn/(a[k]*r[k]+b[k]);Y[k]=X[k]/r[k];/*Y[k]=maxn/(a[k]*r[k]+b[k]);X[k]=Y[k]*r[k];*/ // 这里巨恶心……如果按我注释掉的方法写的话有一个点会 WA ……int pr=g_pre(k),nx=g_nxt(k);if(nx&&g_k(pr,k)<=g_k(nx,k)) return;if(!insert(rt,k)) return;while(pre[k]&&tk[k]>tk[pre[k]]){del(rt,pre[k]);pre[k]=g_pre(k);if(pre[k]) nxt[pre[k]]=k;tk[k]=g_k(k,pre[k]);}while(nxt[nxt[k]]&&tk[nxt[k]]<tk[nxt[nxt[k]]]){del(rt,nxt[k]);nxt[k]=g_nxt(k);if(nxt[k]) pre[nxt[k]]=k;if(nxt[k]) tk[nxt[k]]=g_k(k,nxt[k]);}}int k_pre(int k){int u=rt,ans=0;while(u){if(tk[u]>K[k]){ ans=u;u=rc[u]; }else u=lc[u];}return ans;}void update(int i){K[i]=-a[i]/b[i];int j=k_pre(i);f[i]=a[i]*X[j]+b[i]*Y[j];}void check(){cin>>n;for(int i=1;i<=n;i++) scanf("%lf%lf",&X[i],&Y[i]);for(int i=1;i<=n;i++) add(i);}int main(){n=(int)read(),f[1]=read();maxn=f[1];for(int i=1;i<=n;i++){a[i]=read();b[i]=read();r[i]=read();}add(1);for(int i=2;i<=n;i++){update(i);maxn=max(maxn,f[i]);add(i);}printf("%.3lf\n",maxn);return 0;}