题目描述
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;
}