Luogu7116 [NOIP2020] 微信步数

Luogu7116 [NOIP2020] 微信步数

拉格朗日插值

\(NOIP\)题咕了这么久,我果然是咕咕怪。

把题意进行转化,可以发现就是求每一天中存活的坐标数量。

我们的思路是,对每一步,同时计算它在多轮贡献,那么就需要计算出每一步能够走多少轮。

\(l_{i,j,k},r_{i,j,k}\)分别表示第\(i\)轮,第\(j\)步,第\(k\)维的存活区间的左右端点。

那么这一步的贡献就是对应的一个超立方体内的点数。

容易发现,对于一步来说,从第二轮开始会出现每次减少一定范围的情况,我们可以把它列出式子:

\[\sum_{x=0}^T \prod_i A-Bx-C_i \]

\(T\)为最多走的轮数,\(A\)为第一轮后存活的点数,\(B\)为每轮死亡的点数,\(C_i\)当前这一维度,第二轮中到达枚举的这步时已经死亡的点数。

可以先算出关于\(x\)的多项式,然后利用拉格朗日插值算出自然数幂前缀和,即可快速计算贡献。

时间复杂度:\(O(nk^2)\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 500005
#define ll long long
using namespace std;
const int p=1000000007;
const int INF=1000000007;
int n,k,ans,w[N],c[N],d[N],e[15],a[15],b[15];
int l[N][15],r[N][15];
int x[15],y[15],z[15],f[15];
int inv[15],fac[15],infac[15];
int pre[15],suf[15],E[15][15];
void Add(int &x,int y)
{
    x=(x+y)%p;
}
void Del(int &x,int y)
{
    x=(x-y)%p;
}
void Mul(int &x,int y)
{
    x=(ll)x*y%p;
}
int add(int x,int y)
{
    return (x+y)%p;
}
int del(int x,int y)
{
    return (x-y)%p;
}
int mul(int x,int y)
{
    return (ll)x*y%p;
}
int ksm(int x,int y)
{
    int ans(1);
    while (y)
    {
        if (y & 1)
            Mul(ans,x);
        Mul(x,x);
        y >>=1;
    }
    return ans;
}
void ckmin(int &x,int y)
{
    x=(x<y)?x:y;
}
void ckmax(int &x,int y)
{
    x=(x>y)?x:y;
}
int calc(int n,int m)
{
    int ans(0);
    for (int i=1;i<=m+2;++i)
        y[i]=add(y[i-1],E[i-1][m]);
    pre[0]=1;
    for (int i=1;i<=m+2;++i)
        pre[i]=mul(pre[i-1],n-i+1);
    suf[m+3]=1;
    for (int i=m+2;i;--i)
        suf[i]=mul(suf[i+1],n-i+1);
    for (int i=1;i<=m+2;++i)
    {
        int t(mul(infac[i-1],infac[m+1-i+1]));
        if ((m+1-i+1) & 1)
            t=-t;
        Mul(t,y[i]);
        Add(ans,mul(t,mul(pre[i-1],suf[i+1])));
    }
    return ans;
}
int main()
{
    inv[1]=fac[0]=fac[1]=infac[0]=infac[1]=1;
    for (int i=2;i<=14;++i)
        inv[i]=mul(p-p/i,inv[p%i]),fac[i]=mul(fac[i-1],i),infac[i]=mul(infac[i-1],inv[i]);
    for (int i=0;i<=14;++i)
        for (int j=0;j<=14;++j)
            E[i][j]=ksm(i,j);
    scanf("%d%d",&n,&k);
    for (int i=1;i<=k;++i)
        scanf("%d",&w[i]);
    for (int i=1;i<=n;++i)
    {
        scanf("%d%d",&c[i],&d[i]);
        e[c[i]]+=d[i];
        memcpy(l[i],l[i-1],sizeof(l[i-1]));
        memcpy(r[i],r[i-1],sizeof(r[i-1]));
        ckmin(l[i][c[i]],e[c[i]]);
        ckmax(r[i][c[i]],e[c[i]]);
    }
    bool flag=true;
    for (int i=1;i<=k;++i)
        flag&=(!e[i] && l[n][i]>=-w[i] && r[n][i]<=w[i]);
    if (flag)
    {
        puts("-1");
        return 0;
    }
    for (int i=1;i<=k;++i)
        a[i]=w[i]-(r[n][i]-l[n][i]);
    for (int i=0;i<=n;++i)
    {
        int cnt(1);
        for (int j=1;j<=k;++j)
            Mul(cnt,max(w[j]-(r[i][j]-l[i][j]),0));
        Add(ans,cnt);
    }
    for (int i=1;i<=n;++i)
        for (int j=1;j<=k;++j)
        {
            l[i][j]=min(0,l[i][j]+e[j]-l[n][j]);
            r[i][j]=max(0,r[i][j]+e[j]-r[n][j]);
        }
    for (int i=1;i<=k;++i)
        b[i]=r[n][i]-l[n][i];
    int lst(-1);
    for (int i=1;i<=n;++i)
    {
        int T(INF);
        memset(f,0,sizeof(f)),f[0]=1;
        bool flag=true;
        for (int j=1;j<=k;++j)
        {
            int xs(a[j]-(r[i][j]-l[i][j]));
            if (xs<=0)
            {
                flag=false;
                break;
            }
            if (b[j]>0)
                ckmin(T,xs/b[j]);
            for (int t=k;t>=0;--t)
            {
                Mul(f[t],xs);
                if (t)
                    Add(f[t],mul(f[t-1],-b[j]));
            }
        }
        if (!flag)
            break;
        if (lst!=T)
        {
            for (int j=0;j<=k;++j)
                z[j]=calc(T,j);
            lst=T;
        }
        for (int j=0;j<=k;++j)
            Add(ans,mul(f[j],z[j]));
    }
    Add(ans,p);
    printf("%d\n",ans);
    return 0;
}
posted @ 2021-04-13 20:58  GK0328  阅读(77)  评论(0编辑  收藏  举报