Hdu 5236 Article(dp)

题目链接

Article

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 404    Accepted Submission(s): 119


Problem Description
As the term is going to end, DRD begins to write his final article.

DRD uses the famous Macrohard's software, World, to write his article. Unfortunately this software is rather unstable, and it always crashes. DRD needs to write n characters in his article. He can press a key to input a character at time i+0.1 , where i is an integer equal or greater than 0. But at every time i0.1 for integer i strictly greater than 0, World might crash with probability p and DRD loses his work, so he maybe has to restart from his latest saved article. To prevent write it again and again, DRD can press Ctrl-S to save his document at time i . Due to the strange keyboard DRD uses, to press Ctrl-S he needs to press x characters. If DRD has input his total article, he has to press Ctrl-S to save the document.

Since World crashes too often, now he is asking his friend ATM for the optimal strategy to input his article. A strategy is measured by its expectation keys DRD needs to press.

Note that DRD can press a key at fast enough speed.
 

Input
First line: an positive integer 0T20 indicating the number of cases.
Next T lines: each line has a positive integer n105 , a positive real 0.1p0.9 , and a positive integer x100 .
 

Output
For each test case: output ''Case #k: ans'' (without quotes), where k is the number of the test cases, and ans is the expectation of keys of the optimal strategy.
Your answer is considered correct if and only if the absolute error or the relative error is smaller than 106 .
 

Sample Input
  
   
   
2 1 0.5 2 2 0.4 2
 

Sample Output
  
   
   
Case #1: 4.000000 Case #2: 6.444444
 

Source

题意:要敲n个字符的文本,每按一个键以后,可以选择是否保存当前文本,若要保存需要按x个键(不会崩溃)。然后有p的概率文本崩溃,崩溃以后文本回到最近保存的状态。有(1-p)的概率没有崩溃。。然后继续输入下一个字符。求输入完整个文本最少按键的期望次数?

题解:用f[i]表示一次不保存并输入长度为i的文本的按键的期望次数。则:

f[i]=(f[i-1]+1)(1-p)+(f[i-1]+1+f[i])*p;

f[i]=(f[i-1]+1)/(1-p);

接下来我们有两种方法。

方法一:用dp[i]表示输入长度为i的文本且一定保存好的最少期望按键次数。那么:

dp[i]=min(dp[i],dp[k]+f[i-k]+x);

用w(i,j)表示f[i-j],可以证明:

扫描二维码关注公众号,回复: 12638659 查看本文章

w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1);

即 我们要证明:2*f[x]<=f[x-1]+f[x+1];

因为:

f[x-1]=(1-p)*f[x]-1;

f[x+1]=(f[x]+1)/(1-p);

所以我们需要证明:2*f[x]<=(1-p)*f[x]-1+f[x]/(1-p)+1/(1-p);

即证明:-p^2/(1-p)*f[x]<=1/(1-p);

显然上式式成立的。

所以w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1)也成立。

所以dp[i]满足决策单调。我们可以用O(nlogn)的方法解决此题。

对于1D/1D动态规划问题,如何用决策单调的性质优化复杂度。可以看论文《1D/1D动态规划优化初步》。

代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int nn=110000;
const double eps=1e-8;
double f[nn];
double dp[nn];
int n,x;
double p;
double a[nn],b[nn];
void init()
{
    f[0]=0;
    for(int i=1;i<=n;i++)
    {
        f[i]=(1.0+f[i-1])/(1-p);
    }
}
struct node
{
    int l,r;
    int val;
    node(){}
    node(int ll,int rr,int vall)
    {
        l=ll,r=rr,val=vall;
    }
}que[nn];
int head,last;
bool check(int id,int v1,int v2)
{
    return (dp[v1]+f[id-v1]-(dp[v2]+f[id-v2]))<0;
}
void add(int l,int r,int val)
{
    while(head<last)
    {
        if(check(que[last-1].l,que[last-1].val,val))
        {
            int xx=que[last-1].l,y=que[last-1].r;
            int mid;
            while(xx<y)
            {
                mid=(xx+y+1)/2;
                if(check(mid,que[last-1].val,val))
                {
                    xx=mid;
                }
                else
                    y=mid-1;
            }
            que[last-1].r=xx;
            l=xx+1;
            break;
        }
        else
        {
            l=que[last-1].l;
            last--;
        }
    }
    if(l<n+1)
    {
        que[last++]=node(l,n,val);
    }
}
int main()
{
    int t,cas=1;
    int i;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%lf%d",&n,&p,&x);
        init();
        head=last=0;
        add(1,n,0);
        for(i=1;i<=n;i++)
        {
            while(head<last)
            {
                if(que[head].r>=i)
                    break;
                head++;
            }
            dp[i]=dp[que[head].val]+f[i-que[head].val]+x;
            que[head].l=i+1;
            if(que[head].l>que[head].r)
                head++;
            add(n+1,n+1,i);
        }
        printf("Case #%d: %.6lf\n",cas++,dp[n]);
    }
    return 0;
}

方法二:枚举保存了几次,容易证明让几次保存平均分配是最优的。代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int nn=110000;
const double eps=1e-8;
double f[nn];
double dp[nn];
int n,x;
double p;
double a[nn],b[nn];
void init()
{
    f[0]=0;
    for(int i=1;i<=n;i++)
    {
        f[i]=(1.0+f[i-1])/(1-p);
    }
}
int main()
{
    int t,cas=1;
    int i;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%lf%d",&n,&p,&x);
        init();
        double ans=1e20;
        for(i=0;i<=n-1;i++)
        {
            int ix=n/(i+1);
            int fc=n%(i+1);
            double tem=fc*f[ix+1]+(i+1-fc)*f[ix]+(i+1)*x;
            ans=min(ans,tem);
        }
        printf("Case #%d: %.6lf\n",cas++,ans);
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/madaidao/article/details/46472463