• 2016 多校5 ATM


    2016 多校5 ATM

    题意:

    有个人富到不知道自己有多少钱,但是知道钱数(xin  cap [0,K])

    它最多可以有(W)次查询超过钱数,(Wge 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)

    因此,转移的表达式应是(egin{aligned} dp_{i,j}=min_{k=1}^ilbrace dp_{k-1,j-1}+dp_{i-k,j}+i+1 braceend{aligned})

    因为不是期望而是计数,决策应该更好理解了吧

    直接转移,复杂度为(O(n^3))

    优化1

    不会证明,但是(dp_{k-1,j-1}+dp_{i-k,j})构成了关于(k)斜率单调非递减的函数(俗称单峰函数),可以直接三分

    复杂度为(O(n^2log n))

    #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
    ",(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))

    #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) { // 从上一个决策位置开始for
                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
    ",(ldb)dp[n][m]/(n+1));
    }
     
     
    

    优化3:第二维大小的优化

    我们知道,存在一种决策方法即每次二分上界,可以取到一个较优值

    满足这个决策只需要(Wge log_2 k),大致可以认为(Wge 10)

    在最优决策的情况下,一定可以在(10)次错误的范围内查出结果,即(Wge 10)之后(W)的值已经不会影响答案了

    所以直接上优化,转移复杂度就是(O(n^2log n))

    加上决策单调性的优化,就是(O(nlog n))

    #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
    ",1.0*dp[n][min(m,10)]/(n+1));
    }
    
    

    以下是来自地狱的魔改代码

    #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
    ",1.0*dp[m>10?10:m][n]/(n+1));
    }
    
    
  • 相关阅读:
    Java 7的javax.net.ssl.SSLHandshakeException
    Oracle数据泵导出数据库
    ORA-00054: 资源正忙 --锁表的解决方法
    Linux学习私人笔记-Shell基础
    Linux学习私人笔记-目录和文件的基本命令
    Linux学习私人笔记-文件基本命令
    Linux学习私人笔记-账号管理
    Linux学习私人笔记-Vim
    form提交表单时本地下载
    SQL Servel 中分页解决
  • 原文地址:https://www.cnblogs.com/chasedeath/p/13588052.html
Copyright © 2020-2023  润新知