2014校赛 猫和老鼠

题目描述

 

Tom 和 Jerry 的故事又开始了。一天Jerry在偷得k块奶酪之后要返回自己的家中。现已知Jerry目前的位置在数轴上的坐标为0,Jerry的家在数轴上的坐标为n,Jerry每次只能从i处走到i+1处。初始时刻Jerry在每一点处都有的概率被Tom发现,而在数轴上坐标为i的地方,Jerry可以选择吃掉一块奶酪来提升自己的行动能力。由于距离的原因,Jerry在坐标为i的地方吃掉一块奶酪后可以把在此之后每一点处被Tom发现的概率降低为。现在请你计算Jerry通过适当选择吃奶酪的位置且一共吃了不超过k块奶酪之后能够全程不被Tom发现返回家中的最高概率。由于这个数值可能很小,在本题中请你以科学计数法表示,即表示为的形式,其中,请你输出对应的  即可,其中请保留5位有效数字。输出格式见样例。

输入

       输入数据第一行为一个正整数t(t<=5),表示测试数据总数。

       对于每组测试数据包含2行,第1行为两个整数n,k以及初始概率p,其中1<=n<=2000,0<=k<=1000,0<=p<=1且p精确到小数点以后第四位。第2行包含以空格隔开的n个小数,分别表示在第i点处Jerry吃掉一个奶酪后被沿途被Tom发现的概率,其中且。

输出

       对于每组数据输出一行,包括两个数即题目中描述的,中间用空格隔开,其中,且保留5位有效数字,为一个整数,如果Jerry一定会被Tom发现,请输出 0.00000。

输入样例

2

2 1 0.8000

0.5000 0.2000

2 1 0.8000

0.7000 0.2000

输出样例

2.5000 -1

1.6000 -1


阅读完题目后,很容易想到这是一道动态规划。

很容易想到的dp方程:dp(i ,j ) : 前i个位置上,只吃了j个奶酪,到终点被抓住的最小概率。那么答案就应该是dp(n , k )。

状态转移:dp(i , j) = min(dp(i-1,j) , sigma(dp(p , j-1) * save[p , j-1]^(i-p)))。

那么这个状态转移的复杂度是O(kn^2),在时间范围内无法解决,并且在最后答案的处理上比较繁琐。

那么,我么考虑如下的dp方程:

dp(i , j) : 从第i个点开始,在之后的路途中吃完j个奶酪,安全到达终点的最大概率。

状态转移方程 : dp(i , j) = max(dp(k , j-1)*(1-q[i])^(k-i))。

但是这样的复杂度仍然为O(kn^2)。

考虑在对原dp方程取以10为底的对数。

则原dp方程变为 :dp (i ,j ) = max(dp(k ,j-1 ) + (k - i)*log10(1 - q[i]))。

 我们可以发现这是很显然的由单调队列优化的dp。

证明如下:

考虑i < p < q 

若p 相对与 q是更好的解答。

那么我们有 dp(p , j-1) + (p - i)*log10(1 - q[i]) >= dp(q , j-1) + (q - 1)*(log10(1 - q[i]))。

整理得:(dp(p , j-1) - dp(q , j-1)) / (p-q) <= - log10(1-q[i])。

我们不妨记左边是g(p , q)。

特别地,若g(i , p)<=g(p , q)则必舍去p。

证明:

1.若g(i , p)<= bound,则i比p更好,删p。

2.若g(i , p)> bound,则p 比i 更好,删去 i,但是此时g(p , q)> g(i ,p) > bound,则也需要舍去p。

我们用队列维护dp值的下标,维护策略:

1)当更新某点的值时,我们从队头计算g(queue[head] , queue[head + 1])的值,若小于-log10(1-q[i]),那么我们删除head。

2)当插入某点的值时,我们从尾考虑g(i,p), g(p,q),并进行删点。

这样,我们将时间复杂度降到了O(kn),可以在时间内解决。


#include "cstdio"
#include "cmath"
#include "algorithm"
using namespace std;
#define inf 0xffff

double dp[2005][1005];
double q[2005];
int queue[2005];
int n,k;
double p;
int head,tail;

void input()
{
    scanf("%d%d%lf",&n,&k,&p);
    p=log10(1-p);
    for(int i=0;i<n;i++)
    {
        scanf("%lf",&q[i]);
        q[i]=log10(1-q[i]);
    }
}

void init()
{
    head=tail=0;
}

inline bool check1(int x, int i, int j, double k)
{
    return (dp[i][x] - dp[j][x]) / (double)(i - j) <= k;
}

inline bool check2(int x, int i, int j, int k)
{
    return (dp[i][x] - dp[j][x]) / (double)(i - j) <= (dp[j][x] - dp[k][x]) / (double)(j - k);
}

void solve()
{
    memset(dp,0,sizeof(dp));
    if(k>n) k=n;
    for(int i=n-1;i>=0;i--)  dp[i][0]=dp[i+1][0]+p;
    double ans=dp[0][0];
    for(int j=1;j<=k;j++)
    {
        dp[n][j]=0;
        init();
        queue[tail++]=n-j+1;
        for(int i=n-j;i>=0;i--)
        {
            while(head+1<tail&&check1(j-1,queue[head],queue[head+1],-q[i]))  head++;
            dp[i][j]=dp[queue[head]][j-1]+(double)(queue[head]-i)*q[i];
            //printf("front = %d i = %d j = %d dp = %lf pre = %d ans = %lf q = %lf\n",head,i,j,pow(10.,dp[i][j]),queue[head],pow(10.,ans),q[i]);
            ans=max(ans,dp[i][j]+(i?(double)i*p:0));
            while(head+1<tail&&check2(j-1,i,queue[tail-1],queue[tail-2]))  tail--;
            queue[tail++]=i;
        }
    }
    //printf("%lf\n",ans);
    int tmp=(int)floor(ans);
    if(ans<=-1e16)  printf("0.0000 0\n");
    else  printf("%.4lf %d\n",pow(10.,ans-tmp),tmp);
}

int main()
{
    //freopen("data1.in","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        input();
        solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值