[关闭]
@rebirth1120 2019-08-07T15:16:33.000000Z 字数 3621 阅读 731

[NOI2007] 货币兑换 解题报告

NOI 解题报告 dp


题目链接

题目大意

两种金券,它们每天的价值都不同,在第 天时分别为 ,你可以用手中的金券换取相应价值的钱,或用钱兑换相同价值的金券,但兑换到的两种金券的数量之比是一个定值,这个定值在第 天为
现给出 天中两种金券的价值, ,以及你初始时拥有的资金 ,求 天后你最多能有多少钱。
注:一定存在一种最优方案,满足:每次兑换金券花光所有的钱,每次换钱时花掉所有的金券。

解题思路

一眼dp题(大佬好像都是这样说的)
先设个状态吧,
表示在第 天卖掉所有的金券能获得的最多钱数,那我们只需要决定上一次买金券是在什么时候,转移方程如下:


的值就取决与你上一次是在什么时候买的金券。

假设我们上一次是在第 天买的金券,而上一次卖掉金券是在第 天(),那么我们在第 天拥有的资金就是 ,可以得出式子:


变个形

移个项

很容易看出来, 越大越好,所以我们用个 代替

现在,我们就可以得出完整的转移方程

辣眼睛
一个 算法就完成了,看下数据范围

对于 的测试数据,满足 .....

手动再见

没办法,考虑优化吧。
再看眼式子


有关的和与 有关的粘在了一起,单调队列是没法搞了,斜率优化吧。
把式子转化一下,先把 扔掉,除个 (这第一步我还是看了题解才知道的……)

再移个项

搞定,妥妥的斜率优化,
但,
这里的 都不是递增的,队列搞不了……
怎么办,不会做,

打开题解看一眼,平衡树,cdq,关上。

cdq没有思路,还是用平衡树吧,虽然写起来麻烦点(为了写这道题我又去敲了遍平衡树的板子,又调了一个小时……)

那现在思路就很明了了(可能吧),用平衡树维护一个上凸包,将凸包上的点按横坐标排序,然后每次找地方插入(或不插入),然后删除没用的点就行了,并在每一个点存下它与凸包上前一个点连接形成的直线的斜率 ,更新状态时找到凸包上 比当前直线大的第一个点进行转移就行了(建议自己画个图理解一下)。

这道题貌似到这里就全部解完了,但实际上还有很多细节需要考虑,包括在插入、删除节点是要更新它的前驱和后缀的信息,以及花式卡精度……(数据里竟然出现了两个点的横坐标小数点后十几位都相同的情况……)。

最后这道题做下来前前后后花了可能有两天……

代码实现

  1. #include<iostream>
  2. #include<cstdlib>
  3. #include<cstdio>
  4. #define db double
  5. using namespace std;
  6. const int N=100000+7;
  7. int n,q[N],rt,cnt;
  8. db f[N],a[N],b[N],r[N],X[N],Y[N],K[N],maxn;
  9. int lc[N],rc[N],p[N],pre[N],nxt[N];
  10. db tk[N];
  11. db read(){
  12. char c=getchar(); db f=1,x=0;
  13. while(c<'0'||c>'9'){ f=c=='-'?-1:1;c=getchar(); }
  14. while(c>='0'&&c<='9'){ x=x*10+c-'0';c=getchar(); }
  15. if(c!='.') return f*x;
  16. c=getchar(); db y=1;
  17. while(c>='0'&&c<='9'){ y/=10;x+=(c-'0')*y;c=getchar(); }
  18. return f*x;
  19. }
  20. void zig(int &u){
  21. int ls=lc[u];
  22. lc[u]=rc[ls];
  23. rc[ls]=u;
  24. u=ls;
  25. }
  26. void zag(int &u){
  27. int rs=rc[u];
  28. rc[u]=lc[rs];
  29. lc[rs]=u;
  30. u=rs;
  31. }
  32. int g_pre(int k){
  33. int u=rt,ans=0;
  34. while(u){
  35. if(X[u]<X[k]){ ans=u; u=rc[u]; }
  36. else u=lc[u];
  37. }
  38. return ans;
  39. }
  40. int g_nxt(int k){
  41. int u=rt,ans=0;
  42. while(u){
  43. if(X[u]>X[k]){ ans=u; u=lc[u]; }
  44. else u=rc[u];
  45. }
  46. return ans;
  47. }
  48. db g_k(int a,int b){
  49. if(!a||!b) return 0;
  50. return (Y[a]-Y[b])/(X[a]-X[b]);
  51. }
  52. bool insert(int &u,int k){
  53. if(!u||X[u]==X[k]){
  54. if(X[u]==X[k]&&Y[k]<=Y[u]) return 0;
  55. u=k;
  56. pre[u]=g_pre(u);
  57. nxt[u]=g_nxt(u);
  58. if(pre[u]) nxt[pre[u]]=u;
  59. if(nxt[u]) pre[nxt[u]]=u;
  60. tk[u]=g_k(u,pre[u]);
  61. if(nxt[u]) tk[nxt[u]]=g_k(u,nxt[u]);
  62. p[u]=rand();
  63. return 1;
  64. }
  65. if(X[u]>X[k]){
  66. bool f=insert(lc[u],k);
  67. if(p[lc[u]]<p[u]) zig(u);
  68. return f;
  69. }
  70. else{
  71. bool f=insert(rc[u],k);
  72. if(p[rc[u]]<p[u]) zag(u);
  73. return f;
  74. }
  75. }
  76. void del(int &u,int k){
  77. if(u==k){
  78. if(!lc[u]||!rc[u]){ u=lc[u]+rc[u];return; }
  79. else if(p[lc[u]]<p[rc[u]]){ zig(u);del(rc[u],k); }
  80. else{ zag(u);del(lc[u],k); }
  81. }
  82. else if(X[u]>X[k]) del(lc[u],k);
  83. else del(rc[u],k);
  84. }
  85. void add(int k){
  86. X[k]=r[k]*maxn/(a[k]*r[k]+b[k]);
  87. Y[k]=X[k]/r[k];
  88. /*Y[k]=maxn/(a[k]*r[k]+b[k]);
  89. X[k]=Y[k]*r[k];*/ // 这里巨恶心……如果按我注释掉的方法写的话有一个点会 WA ……
  90. int pr=g_pre(k),nx=g_nxt(k);
  91. if(nx&&g_k(pr,k)<=g_k(nx,k)) return;
  92. if(!insert(rt,k)) return;
  93. while(pre[k]&&tk[k]>tk[pre[k]]){
  94. del(rt,pre[k]);
  95. pre[k]=g_pre(k);
  96. if(pre[k]) nxt[pre[k]]=k;
  97. tk[k]=g_k(k,pre[k]);
  98. }
  99. while(nxt[nxt[k]]&&tk[nxt[k]]<tk[nxt[nxt[k]]]){
  100. del(rt,nxt[k]);
  101. nxt[k]=g_nxt(k);
  102. if(nxt[k]) pre[nxt[k]]=k;
  103. if(nxt[k]) tk[nxt[k]]=g_k(k,nxt[k]);
  104. }
  105. }
  106. int k_pre(int k){
  107. int u=rt,ans=0;
  108. while(u){
  109. if(tk[u]>K[k]){ ans=u;u=rc[u]; }
  110. else u=lc[u];
  111. }
  112. return ans;
  113. }
  114. void update(int i){
  115. K[i]=-a[i]/b[i];
  116. int j=k_pre(i);
  117. f[i]=a[i]*X[j]+b[i]*Y[j];
  118. }
  119. void check(){
  120. cin>>n;
  121. for(int i=1;i<=n;i++) scanf("%lf%lf",&X[i],&Y[i]);
  122. for(int i=1;i<=n;i++) add(i);
  123. }
  124. int main(){
  125. n=(int)read(),f[1]=read();maxn=f[1];
  126. for(int i=1;i<=n;i++){
  127. a[i]=read();
  128. b[i]=read();
  129. r[i]=read();
  130. }
  131. add(1);
  132. for(int i=2;i<=n;i++){
  133. update(i);
  134. maxn=max(maxn,f[i]);
  135. add(i);
  136. }
  137. printf("%.3lf\n",maxn);
  138. return 0;
  139. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注