百度之星2021初赛一 hdu7000 二分 (概率dp+快速IO)
这么过分的题一定要写blog,真就欺负乡下人不会快速IO呗(bushi)
题目传送门:https://acm.hdu.edu.cn/showproblem.php?pid=7000
题意很明显,不再多说。
期望dp常见做法:记g[i]表示i到y的期望步数,显然有:
\[g[i]=\frac{1}{n-i}\sum_{j>i}g[j]+1 (i<y)
\]
\[g[i]=\frac{1}{i-1}\sum_{j<i}g[j]+1 (i>y)
\]
\[g[y]=0
\]
记sum[i]表示g[i]前缀和,把上面的式子变形一下,有:
\[sum[i]=\frac{n-i}{n-i+1}sum[i-1]+\frac{1}{n-i+1}sum[n]+\frac{1}{n-i+1} (i<y)
\]
\[sum[i]=\frac{i}{i-1}sum[i-1]+1 (i>y)
\]
\[sum[y-1]=sum[y]
\]
把前两条式子整理成封闭的递推式,第一条式子里,sum[n]看成常数,有:
\[sum[i]=\frac{i}{n}sum[n]+(n-i)\sum_{j=n-i+1}^{n}\frac{1}{j} (i<y)
\]
\[sum[i]=\frac{i}{y}sum[y]+i\sum_{j=y+1}^{i}\frac{1}{j} (i>y)
\]
将y-1带入第一条式子,将n带入第二条式子,又因为\(sum[y-1]=sum[y]\),得到sum[y]和sum[n]的两条式子,联立解出sum[y]即可。解出sum[y]后,可以依次解出sum[x]和sum[x-1]。需要预处理逆元(这里是线性逆元)和逆元前缀和。
不过下面程序里的式子其实简洁很多,首先注意到本题具有对称性,直接考虑x>y的情况即可,不用再解中间量sum[n]。其次,可以再往下化简,发现也不必再需要中间量sum[y],可以把g[x]的最终形式用一条式子化简出来。中间步骤懒得打了,大家自己推推,有益身心健康,最后得到的是:
\[g[x]=f[n]-f[y]-f[n-y+1]+invsum[x]+1
\]
其中,inv_sum是逆元前缀和,\(f[i]=invsum[i]*i\)。
为什么要化到这么简?不是我精益求精,而是不停TLE。后来发现跟这没关系,是IO占用时间太多了。找Ghastlcon要了个真的IO优化,原先2000ms跑不过的,现在只要998ms。顺带把我原先的辣鸡OI留下来以供参考,压根没用,这tm就是个屑:
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
#define B 32767
namespace IO
{
char a[B], b[B];
int c;
int Getchar(void)
{
static int c = B;
if(c == B)
{
fread(a, sizeof(char), B, stdin);
c = 0;
}
return a[c ++];
}
void Flush(void)
{
fwrite(b, sizeof(char), c, stdout);
c = 0;
return;
}
void Putchar(int x)
{
if(c == B)
Flush();
b[c ++] = x;
return;
}
int Scan(void)
{
int c;
int s;
for(s = 0; (c = Getchar()) < '0' || c > '9'; )
;
do
s = s * 10 + (c & 15);
while((c = Getchar()) >= '0' && c <= '9');
return s;
}
void Print(int x)
{
int c;
static int b[20];
if(!x)
Putchar('0');
else
{
for(c = 0; x; x /= 10)
b[c ++] = x % 10;
while(c --)
Putchar(b[c] + 48);
}
return;
}
}
const LL M=1000000007ll;
const int maxn=2000010;
LL inv[maxn];
LL inv_sum[maxn];
LL f[maxn];
int t,n,x,y;
inline int Read()
{
int s=0;
char ch=getchar();
while(ch<'0'||ch>'9'){ch=getchar();}
while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return s;
}
inline void Print(long long x)
{
if(x > 9) Print(x/10);
putchar(x%10 + '0');
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
inv[1]=1;
inv_sum[1]=1;
f[1]=1;
for (register int i=2; i<maxn; ++i)
{
inv[i]=M-(M/i)*inv[M%i]%M;
inv_sum[i]=inv_sum[i-1]+inv[i];
if (inv_sum[i]>=M) inv_sum[i]-=M;
f[i]=inv_sum[i]*(long long)i%M;
}
scanf("%d",&t);
while (t--)
{
n=IO::Scan();
x=IO::Scan();
y=IO::Scan();
if (x==y)
{
IO::Putchar('0');
IO::Putchar('\n');
continue;
}
if (x<y)
{
x=n-x+1;
y=n-y+1;
}
LL ans=f[n]-f[y]-f[n-y+1]+M+M+inv_sum[x-1]+1;
while (ans>=M) ans-=M;
IO::Print(ans);
IO::Putchar('\n');
}
IO::Flush();
return 0;
}

浙公网安备 33010602011771号