百度之星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;
}
posted @ 2021-08-06 21:11  KsCla  阅读(130)  评论(0)    收藏  举报