2016 多校5 ATM
题意:
有个人富到不知道自己有多少钱,但是知道钱数$x\in \Z \cap [0,K]$
它最多可以有$W$次查询超过钱数,$W\ge 1$
要求在最优决策的情况下,最小次数取出所有钱的期望次数
设$K,W$上界为$O(n)$
先考虑边界情况,如果它手里有$0$块钱,那么需要查询一次才知道自己吃土了
如果手头$W=1$,那么只能每次取$1$,否则就可能被抓走
众所周知,情况个数有限的期望问题,可以先直接计数然后除掉情况数,所以
定义$dp_{i,j}$为已知手头的票子上限$i$,且还剩$j$次会被抓去干奇怪的事情的最小代价总和
对于$j>1$的情况,我们需要决策这一次选出多少钱
假设这一次我们选择取出$k$块钱
1.那么对于实际钱数为$[0,k-1]$的部分,查询会超限,并且知道上界变为$k-1$
2.对于实际钱数$[k,i]$的部分,上界变为$i-k$
而这次决策产生的代价要计算所有情况的代价,即为$i+1$
因此,转移的表达式应是$\begin{aligned} dp_{i,j}=\min_{k=1}^i\lbrace dp_{k-1,j-1}+dp_{i-k,j}+i+1\rbrace\end{aligned}$
因为不是期望而是计数,决策应该更好理解了吧
直接转移,复杂度为$O(n^3)$
优化1
不会证明,但是$dp_{k-1,j-1}+dp_{i-k,j}$构成了关于$k$的 斜率单调非递减的函数(俗称单峰函数),可以直接三分
复杂度为$O(n^2\log n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<bits/stdc++.h> using namespace std; typedef long double ldb; #define reg register #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }
const int N=2e3+10; int n,m; int dp[N][N]; int main(){ rep(i,1,N-1) dp[1][i]=2; rep(i,1,N-1) dp[i][1]=i*(i+1)/2+i; rep(i,2,N-1) rep(j,2,N-1) { dp[i][j]=1e9; int l=1,r=i; cmin(dp[i][j],dp[0][j-1]+dp[i-1][j]+i+1); cmin(dp[i][j],dp[i-1][j-1]+dp[0][j]+i+1); while(l<r) { int a=(l+r)>>1,b=a+1; int x=dp[a-1][j-1]+dp[i-a][j]+i+1,y=dp[b-1][j-1]+dp[i-b][j]+i+1; cmin(dp[i][j],x),cmin(dp[i][j],y); if(x>=y) l=b; else r=a; } } while(~scanf("%d%d",&n,&m)) printf("%.6Lf\n",(ldb)dp[n][m]/(n+1)); }
|
优化2
感性理解,钱的上界越大,显然我们每次最优决策要取出的也就越多
即$dp_{i,j}$的最优决策位置关于$i$ 单调非递减
由于转移的式子比较奇怪,这个题目不好使用决策单调性的分治优化方法(或许很简单吗)
但是由于转移是一个单峰函数,不存在波动的问题,所以可以直接记录决策位置$g_{i,j}$
或者说,就是单峰函数的最值位置是递增的,每次从$g_{i-1,j}$的最优位置开始向后找到$g_{i,j}$的峰的位置即可停止
对于每个$j$,$g_{i,j}$最多从$1$移动到$K$,所以复杂度为$O(n^2)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include<bits/stdc++.h> using namespace std; typedef long double ldb; #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); } const int N=2e3+10; int n,m; int dp[N][N],G[N][N]; int main(){ rep(i,1,N-1) dp[1][i]=2; rep(i,1,N-1) dp[i][1]=i*(i+1)/2+i; rep(i,2,N-1) rep(j,2,N-1) { dp[i][j]=1e9; rep(k,max(G[i-1][j],1),i) { int x=dp[k-1][j-1]+dp[i-k][j]+i+1; if(x<=dp[i][j]) G[i][j]=k,dp[i][j]=x; else break; } } while(~scanf("%d%d",&n,&m)) printf("%.6Lf\n",(ldb)dp[n][m]/(n+1)); }
|
优化3:第二维大小的优化
我们知道,存在一种决策方法即每次二分上界,可以取到一个较优值
满足这个决策只需要$W\ge \log_2 k$,大致可以认为$W\ge 10$
在最优决策的情况下,一定可以在$10$次错误的范围内查出结果,即$W\ge 10$之后$W$的值已经不会影响答案了
所以直接上优化,转移复杂度就是$O(n^2\log n)$
加上决策单调性的优化,就是$O(n\log n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include<bits/stdc++.h> using namespace std; #define reg register #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }
const int N=2e3+10;
int n,m; int dp[N][11],G[N][11];
int main(){ rep(i,1,10) dp[1][i]=2; rep(i,1,N-1) dp[i][1]=i*(i+1)/2+i; rep(i,2,N-1) rep(j,2,10) { dp[i][j]=1e9; rep(k,max(G[i-1][j],1),i) { int x=dp[k-1][j-1]+dp[i-k][j]+i+1; if(x<=dp[i][j]) G[i][j]=k,dp[i][j]=x; else break; } } while(~scanf("%d%d",&n,&m)) printf("%.6lf\n",1.0*dp[n][min(m,10)]/(n+1)); }
|
以下是来自地狱的魔改代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include<cstdio> #define rep(i,a,b) for(i=a;i<=b;++i) enum{N=2000}; int n,m,dp[11][N|1],i,j,k,x; main(){ rep(i,1,N) dp[1][i]=i*(i+1)/2+i; rep(j,2,10) rep(i,dp[j][k=1]=2,N) { dp[j][i]=1e9; for(;k<=i && (x=dp[j-1][k-1]+dp[j][i-k]+i+1)<=dp[j][i];++k) dp[j][i]=x; --k; } while(~scanf("%d%d",&n,&m))printf("%.6lf\n",1.0*dp[m>10?10:m][n]/(n+1)); }
|