暂无链接
B君的第一题
【问题描述】
天台上的朋友让一让,让我先来。
世界杯开始了,B 君最近在关注一局季后赛。
在这局季后赛中,甲队和乙队将会进行至多 2n−12n−1 场比赛,谁先取得 nn 场胜利,谁便会获得最终的胜利。并且之后的比赛不会继续进行。
B 君很看好甲队,为了计算方便,B 君希望下注 元,也就是说, 如果最终甲队先取得了nn 场胜利,B 君就会获得 元。如果最终乙队先取得了nn 场胜利,B 君就会失去 元。
但是事不如人愿,甲队和乙队水平旗鼓相当,每一场比赛,双方胜利的概率都是0.50.5,并且所有比赛的结果相互独立。
B 君对此坦然接受,认为自己获得 22n−122n−1 元的概率是 0.50.5,失去22n−122n−1 元的概率是 0.50.5, 期望收益是 00。
但是赌场的庄家并不允许这样做,必须每一场比赛单独下注,分别进行结算。
B 君很失望,但是他发现,存在唯一一种下注方式,达到自己最初的目的。
即如果最终甲队获胜,B 君各场的收益之和为 ,如果最终乙队获胜,B 君各场收益之和为−22n−1−22n−1。
比如对于n=2n=2 来说,B 君可以选择在第一场下注44元,在第二场下注 44 元,在第三场下注 元。
这样如果甲队或乙队赢了前两场,B 君直接获得或失去了88 元。
如果前两场甲乙一胜一负,那么 B 君不赔不赚,决胜局下注 元。
也就是说,无论甲以什么方式获胜,B 君都会获得 88 元。无论乙以什么方式获胜, B 君都会失去 元。
特别的,B 君可以在看到前ii 场的结果之后,再给出第 场的下注,而不需要一 开始就给出所有场的下注情况。
对于输入输出,B 君会先告诉你nn,然后你来告诉B 君第一场比赛应该如何下注, 接下来 B 君告诉你第一场的结果。
你来告诉 B 君第二场比赛应该如何下注,依次类推。如果 B 君告诉你的结果已经导致了甲队或者乙队取得了 场胜利。
你不需要对这次结果做任何回复。
因此你输出的数字个数,和B 君告诉你比赛结果的个数相同。
你可以下注负数,表示下注乙队获胜,这样如果甲队失败便可获得回报。
这并不是一个交互题,每个回答的答案是唯一的。
【输入格式】
第一行一个数字 nn,即上文中所描述的。
第二行是一些00 和 ,如果是00 表示甲队获胜,表示乙队获胜。
保证这是一个合法的比赛过程,也就是说,当出现 00 的个数或 的个数达到nn 个之后, 便不会有更多输入了。
【输出格式】
输出若干个数字,一行一个。个数和输入的第二行数字个数相同,表示每场比赛的下注。
由于下注可能很大,你只需要输出模的结果即可。
即使想下注负数,也应该输出取模后的正余数。
可以证明下注的金额一定是整数。
可以证明解是唯一的。
【输入样例】
3
1 1 0 0 1
【输出样例】
12
12
8
16
32
【样例说明】
显然所有下注的和,要么是+22n−1+22n−1,要么是−22n−1−22n−1。
对于这场(−12)+(−12)+(8)+(16)+(−32)=−32=−25(−12)+(−12)+(8)+(16)+(−32)=−32=−25
【输入样例】
3
0 1 0 0
【输出样例】
12
12
16
16
【样例说明】
(12)+(−12)+(16)+(16)=32=25(12)+(−12)+(16)+(16)=32=25
你可以根据前两场的结果,来动态的决定第三场的下注。
输入的第二行长度并不确定,当输入nn个或nn个时结束。
【数据范围】
对于100%100%的数据,满足1≤n≤1000001≤n≤100000。
对于70%70%的数据,满足1≤n≤10001≤n≤1000。
对于30%30%的数据,满足1≤n≤61≤n≤6。
数据非常有梯度。
题解
以为数据有梯度是区分度很高的意思,部分分有就有,没有就没有,不存在强行造档的情况,所以考场代码如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
freopen("beijing.in","r",stdin);
freopen("beijing.out","w",stdout);
while(1)puts("sh*t");
}
然而出题人居然良心发现,“梯度”的意思居然是n=1,2,3n=1,2,3都有!,根据题目打个1,2,31,2,3的表都有1515分,令人窒息的操作。。。
唉,我dpdp还是太菜了,O(n2)O(n2)的dpdp都想不出来。。。
先考虑O(n2)O(n2)的dpdp吧,我们可以算算甲乙两队赢的概率,设dp[i][j]dp[i][j]为甲队胜ii场,乙队胜场时,甲队取得最终胜利的概率,那么就有dp[i][j]=dp[i+1][j]+dp[i][j+1]2dp[i][j]=dp[i+1][j]+dp[i][j+1]2,即dp[i+1][j]−dp[i][j]=dp[i][j]−dp[i][j+1]dp[i+1][j]−dp[i][j]=dp[i][j]−dp[i][j+1],所以无论输赢,甲队的胜率变化是一样的。
初始情况dp[0][0]dp[0][0]胜率为1212,末状态为00或,收益为−22n−1−22n−1或22n−122n−1,胜率每变化1212,收益变化22n−122n−1,那么对于一次变化ΔxΔx,我们就应该应该下注Δx1222n−1=2Δx×22n−1Δx1222n−1=2Δx×22n−1元。
然后我们就有了一个优秀的O(n2)dpO(n2)dp做法。
其实,获胜概率还可以用组合数算出来,考虑当甲队赢了ii场,乙队赢了场时,对于剩下的res=2n−1−i−jres=2n−1−i−j场比赛,如果甲队取得了n−in−i或更多的场的胜利,就能获胜,所以甲队的胜率可以表示如下:
组合意义很明显,赢得n−i∼resn−i∼res场的情况比上所有情况。
似乎还是无从下手,考虑我们dpdp的思路,如果甲队多赢了一场,胜率将变为:
如果乙队赢了:
发现这两个式子的差只有一项:
代入dpdp得到的结论中,可知我们应下注:
可以预处理组合数,也可以一乘一除得到下一项,复杂度O(n)O(n)。
代码
#include<cstdio>
#define ll long long
using namespace std;
const int M=2e5+5,mod=1e9+7;
int inv[M],x,y,n,p;
ll C(int a,int b){ll ans=1;for(int i=0;i<b;++i)ans=ans*(a-i)%mod*inv[i+1]%mod;return ans;}
void in(){scanf("%d",&n);}
void ac()
{
inv[1]=1;for(int i=2;i<=n+n;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
x=y=n;ll ans=C(x+y-2,x-1)*2%mod;
while(x>0&&y>0)
{
printf("%lld\n",ans);scanf("%d",&p);
ans=ans*2*inv[x+y-2]%mod;
if(p)ans=ans*--y%mod;else ans=ans*--x%mod;
}
}
int main(){in();ac();}