帮直线数一数有多少点被它压在身下吧~

· · 算法·理论

有时候直线在数轴上扑到了很多点,但太多了~

而我们又想知道,它吧多少点压住了,怎么办呢~

类欧几里得

学了这个其实就差不多了。

part1 基础

万恶之源追根溯源,我们想要解决这个问题:

f(N,a,b,c)=\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor

注意到若 a,b 有大于 c 的,则:

\begin{aligned} f(N,a,b,c)&=\sum_{i=0}^{N}\lfloor \frac{(a \bmod c+ \lfloor \frac{a}{c}\rfloor c)i+b \bmod c+\lfloor \frac{b}{c}\rfloor c}{c} \rfloor\\ &=\sum_{i=0}^{N}\lfloor \frac{i(a \bmod c) +b \bmod c+}{c} \rfloor+ i\lfloor\frac{a}{c}\rfloor +\lfloor \frac{b}{c}\rfloor\\ &=f(N,a \bmod c,b\bmod c,c)+\frac{N(N+1)}{2}\lfloor\frac{a}{c}\rfloor +(N+1)\lfloor \frac{b}{c}\rfloor \end{aligned}

于是 a,b 都小于 c 了,然后就是推。。。接下来令 M=\lfloor \frac{an+b}{c} \rfloor

\begin{aligned} f(N,a,b,c)&=\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor\\ &=\sum_{i=0}^{N} \sum_{j=1}^{M} [j\le \lfloor \frac{ai+b}{c} \rfloor]\\ &=\sum_{j=1}^{M} \sum_{i=0}^{N} [j\le \lfloor \frac{ai+b}{c} \rfloor]\\ &=\sum_{j=0}^{M-1} \sum_{i=0}^{N} [jc+c < ai+b+1]\\ &=\sum_{j=0}^{M-1} \sum_{i=0}^{N}[i> \lfloor \frac{jc+c-b-1}{a} \rfloor ]\\ &=\sum_{j=0}^{M-1} N-\lfloor \frac{jc+c-b-1}{a} \rfloor \\ &=NM-f(M-1,c,c-b-1,a) \end{aligned}

注意到最终的式子很像欧几里得算法辗转相减法,故得名类欧几里得,所以复杂度就是 O(\log N)

然后就是一些扩展:

g(N,a,b,c)=\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor ^2\\ h(N,a,b,c)=\sum_{i=0}^{N}i\lfloor \frac{ai+b}{c} \rfloor

类似的推导,以 g 为例,以下默认 a,b\le c

\begin{aligned} g(N,a,b,c)&=\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor ^2\\ &=2\sum_{i=0}^{N} \sum_{j=1}^{\lfloor \frac{ai+b}{c} \rfloor}j -\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor\\ &=2\sum_{i=0}^N\sum_{j=1}^{M} j[j\le\lfloor\frac{ai+b}{c}\rfloor]-f(N,a,b,c)\\ &=2\sum_{i=0}^N\sum_{j=0}^{M-1} (j+1)[jc+c<ai+b+1]-f(N,a,b,c)\\\ &=2\sum_{j=0}^{M-1} (j+1)\sum_{i=0}^N [i>\lfloor\frac{jc+c-b-1}{a}\rfloor]-f(N,a,b,c)\\ &=2\sum_{j=0}^{M-1} (j+1)(N-\lfloor\frac{jc+c-b-1}{a}\rfloor)-f(N,a,b,c)\\ &=NM(M+1)-f(N,a,b,c)-2h(M-1,c,c-b-1,a)-2f(M-1,c,c-b-1,a) \end{aligned}

其他都是类似的,于是我们可以总结出式子了。

先回顾一下定义:

\begin{aligned} f(N,a,b,c)&=\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor\\ g(N,a,b,c)&=\sum_{i=0}^{N}\lfloor \frac{ai+b}{c} \rfloor ^2\\ h(N,a,b,c)&=\sum_{i=0}^{N}i\lfloor \frac{ai+b}{c} \rfloor\\ M&=\lfloor\frac{aN+b}{c}\rfloor \end{aligned}

最终,我们得到了:

\begin{aligned} f(N,a,b,c)&= \begin{cases} (N+1)\lfloor\frac{b}{c}\rfloor &,a=0\\ \frac{N(N+1)}{2}\lfloor\frac{a}{c}\rfloor+(N+1)\lfloor\frac{b}{c}\rfloor+f(N,a\bmod c,b\bmod c,c)&,a\ge c \lor b\ge c\\ NM-f(M-1,c,c-b-1,a)&,\text{otherwise} \end{cases} \\ g(N,a,b,c)&= \begin{cases} (N+1)\lfloor\frac{b}{c}\rfloor^2 &,a=0\\ g(N,a\bmod c,b\bmod c,c)+2\lfloor\frac{a}{c}\rfloor h(N,a\bmod c,b\bmod c,c)+2\lfloor\frac{b}{c}\rfloor f(N,a\bmod c,b\bmod c,c)+\frac{N(N+1)(2N+1)}{6}\lfloor\frac{a}{c}\rfloor^2+ N(N+1)\lfloor\frac{a}{c}\rfloor\lfloor\frac{b}{c}\rfloor+(N+1)\lfloor\frac{b}{c}\rfloor^2&,a\ge c \lor b\ge c\\ NM(M+1)-f(N,a,b,c)-2h(M-1,c,c-b-1,a)-2f(M-1,c,c-b-1,a)&,\text{otherwise} \end{cases} \\ h(N,a,b,c)&= \begin{cases}\frac{N(N+1)}{2}\lfloor\frac{b}{c}\rfloor&,a=0 \\h(N,a\bmod c,b\bmod c,c)+\frac{N(N+1)(2N+1)}{6}\lfloor\frac{a}{c}\rfloor+\frac{N(N+1)}{2}\lfloor\frac{b}{c}\rfloor&,a\ge c \lor b\ge c\\ \dfrac{1}{2}\bigg(MN(N+1)-g(M-1,c,c-b-1,a)-f(M-1,c,c-b-1,a)\bigg)&,\text{otherwise} \end{cases} \end{aligned}

别担心,被吓哭是正常的。

其实仔细思考一下还是比较容易理解的。

发现可以一起递归求出 f,g,h,故得到了模板题的代码。

:::success[code]

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=998244353,inv2=499122177,inv6=166374059;
struct Node{int f,g,h;}ans;
inline int sqr(int x){return 1ll*x*x%Mod;}
inline Node asgcd(int n,int a,int b,int c)
{
    Node res;
    int mul=n*(n+1)%Mod*inv2%Mod; 
    int a_c=a/c;
    int b_c=b/c;
    if(!a)
    {
        res.f=b_c*(n+1)%Mod;
        res.g=sqr(b_c)*(n+1)%Mod;
        res.h=mul*b_c%Mod;
    }
    else if(a>=c||b>=c)
    {
        Node calc=asgcd(n,a%c,b%c,c);
        res.f=(calc.f+mul*a_c+b_c*(n+1))%Mod;
        res.g=(calc.g+2*a_c*calc.h+2*b_c*calc.f+n*(n+1)%Mod*(n*2+1)%Mod*inv6%Mod*sqr(a_c)+n*(n+1)%Mod*a_c%Mod*b_c+(n+1)*sqr(b_c))%Mod;
        res.h=(calc.h+a_c*n%Mod*(n+1)%Mod*(n*2+1)%Mod*inv6+mul*b_c)%Mod;
    }
    else
    {
        int m=(a*n+b)/c;
        Node calc=asgcd(m-1,c,c-b-1,a);
        res.f=(n*m-calc.f+Mod)%Mod;
        res.g=((n*m%Mod*(m+1)-2*(calc.h+calc.f)-res.f)%Mod+Mod)%Mod;
        res.h=((m*n%Mod*(n+1)-calc.g-calc.f)%Mod+Mod)*inv2%Mod;
    }
    return res;
}
int n,a,b,c;
inline void solve()
{
    cin>>n>>a>>b>>c;
    ans=asgcd(n,a,b,c);
    cout<<ans.f<<' '<<ans.g<<' '<<ans.h<<'\n';
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;
    cin>>T;
    while(T--)solve();
}

:::

然后看看 loj 上的模板吧。

求:

f(N,a,b,c,k_1,k_2)=\sum_{i=0}^{N} i^{k_1} \lfloor \frac{ai+b}{c} \rfloor ^{k_2}

其中 k_1+k_2 \le 10

有点麻烦,没有关系,其实是一样的。

然后就是 $a,b\ge c$ 的情况,接下来令 $V_i=\lfloor \frac{(a \bmod c)i+b \bmod c}{c} \rfloor,A=\lfloor \frac{a}{c}\rfloor,B=\lfloor \frac{b}{c}\rfloor$。 $$ \begin{aligned} f(N,a,b,c,k_1,k_2)&=\sum_{i=0}^{N} i^{k_1} \lfloor \frac{ai+b}{c} \rfloor ^{k_2}\\ &=\sum_{i=0}^{N} i^{k_1} \sum_{x+y+z=k_2} \frac{k_2!}{x!y!z!} V_i^x (iA)^yB^z\\ &=k_2!\sum_{x=0}^{k2} \sum_{y=0}^{k_2-x} \frac{A^yB^{k_2-x-y} }{x!y!(k_2-x-y)!}\sum_{i=0}^{N} V_i^xi^{k_1+y}\\ &=k_2!\sum_{x=0}^{k2} \sum_{y=0}^{k_2-x}\dfrac{ A^yB^{k_2-x-y}f(N,a \bmod c,b \bmod c,c,k_1+y,x)}{x!y!(k_2-x-y)!} \end{aligned} $$ 然后又是令 $M=\lfloor \frac{an+b}{c} \rfloor $。 然后利用伯努利数公式:$\dfrac{1}{k+1}\bigg(\displaystyle \sum_{i=1}^{k+1} B^+_{k+1-i} \binom{k+1}{i}n^{i}\bigg)=\sum_{i=1}^{n}i^k$(注意:由于我们特判了 $a=0,k_2=0$ 的情况,所以这样带进去是对的,不用加上 $[k=0]$ 这一项)变形一下,得到: $$ n^{k}=k\sum_{i=1}^{n}i^{k-1}-\sum_{i=1}^{k-1}B^+_{k-i}\binom{k}{i}n^i $$ 用这个来化简。 继续! $$ \begin{aligned} f(N,a,b,c,k_1,k_2)&=\sum_{i=0}^{N} i^{k_1} \lfloor \frac{ai+b}{c} \rfloor ^{k_2}\\ &=k_2\sum_{i=0}^{N} i^{k_1} \sum_{j=1}^{M}j^{k_2-1}[j\le \lfloor \frac{ai+b}{c} ]\rfloor-\sum_{K=1}^{k_2-1} B^+_{k_2-K}\binom{k_2}{K} f(N,a,b,c,k_1,K)\\ \end{aligned} $$ 不用管后面,继续: $$ \begin{aligned} k_2\sum_{i=0}^{N} i^{k_1} \sum_{j=1}^{M}j^{k_2-1}[j\le \lfloor \frac{ai+b}{c} ]\rfloor &=k_2\sum_{j=1}^{M} \sum_{i=0}^{N} i^{k_1} j^{k_2-1}[j\le \lfloor \frac{ai+b}{c} ]\rfloor\\ &=k_2\sum_{j=0}^{M-1} \sum_{i=0}^{N} i^{k_1} (j+1)^{k_2-1}[i> \lfloor \frac{jc+c-b-1}{a} \rfloor ]\\ &=k_2\sum_{j=0}^{M-1}(j+1)^{k_2-1} \sum_{i=0}^{N} i^{k_1} [i > \lfloor \frac{jc+c-b-1}{a} \rfloor ]\\ \end{aligned} $$ 然后依旧伯努利数,化简吧! 接下来记 $\displaystyle S(n,k)=\sum_{i=1}^{n} i^k= \frac{1}{k+1} \Bigg(\sum_{i=1}^{k+1} \binom{k+1}{i}B^+_{k+1-i}n^i \Bigg),T_j=\lfloor \frac{jc+c-b-1}{a} \rfloor

接下来就是带入了。

\begin{aligned} k_2\sum_{j=0}^{M-1}(j+1)^{k_2-1} \sum_{i=0}^{N} i^{k_1} [i > \lfloor \frac{jc+c-b-1}{a} \rfloor ] &=k_2\sum_{j=0}^{M-1}(j+1)^{k_2-1} (S(N,k_1)-S(T_j,k_1))\\ &=k_2\bigg(S(M,k_2-1)S(N,k_1) -\sum_{j=0}^{M-1}(j+1)^{k_2-1} S(T_j,k_1) \bigg)\\ &=k_2\bigg(S(M,k_2-1)S(N,k_1) -\sum_{j=0}^{M-1}(j+1)^{k_2-1} S(T_j,k_1) \bigg) \end{aligned}

把后面的拎出来:

\begin{aligned} \sum_{j=0}^{M-1}(j+1)^{k_2-1} S(T_j,k_1)\ &=\sum_{i=0}^{k_2-1} \sum_{j=0}^{M-1} \binom{k_2-1}{i}j^iS(T_j,k_1) \\ &=\sum_{i=0}^{k_2-1}\binom{k_2-1}{i} \sum_{j=0}^{M-1}j^i \frac{1}{k_1+1} \Bigg([k_1=0]+\sum_{t=1}^{k_1+1} \binom{k_1+1}{t}B^+_{k_1+1-t}T_j^t \Bigg)\\ &=\frac{1}{k_1+1}\sum_{i=0}^{k_2-1} \binom{k_2-1}{i} \sum_{t=1}^{k_1+1} \binom{k_1+1}{t}B^+_{k_1+1-t}\sum_{i=0}^{M-1}j^iT_j^t+[k_1=0]\sum_{i=0}^{k_2-1}\binom{k_2-1}{i} S(m-1,i) \\ &=\frac{1}{k_1+1}\sum_{i=0}^{k_2-1} \binom{k_2-1}{i} \sum_{t=1}^{k_1+1} \binom{k_1+1}{t}B^+_{k_1+1-t} f(M-1,c,c-b-1,a,i,t)+[k_1=0]\sum_{i=0}^{k_2-1}\binom{k_2-1}{i} S(m-1,i)\\ \end{aligned}

终于!

f(N,a,b,c,k_1,k_2)=k_2\bigg(S(M,k_2-1)S(N,k_1) -\frac{1}{k_1+1}\sum_{i=0}^{k_2-1} \binom{k_2-1}{i} \sum_{t=1}^{k_1+1} \binom{k_1+1}{t}B^+_{k_1+1-t} f(M-1,c,c-b-1,a,i,t)-[k_1=0]\sum_{i=0}^{k_2-1}\binom{k_2-1}{i} S(m-1,i) \bigg )-\sum_{K=1}^{k_2-1} B^+_{k_2-K}\binom{k_2}{K} f(N,a,b,c,k_1,K)\\

于是就解决了,虽然套路,但挺复杂的。

:::success[code] 实现比较垃圾,是直接记忆化而不是一起递归,跑得比较慢。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=1e9+7;
inline int ks(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)res=res*a%Mod;
        a=a*a%Mod;
        b>>=1;
    }
    return res;
}
int B[22],fac[22],ifac[22],inv[22],C[22][22];
inline int getpw(int n,int k)
{
    if(!k)return n+1;
    int res=0,pw=n;
    for(int i=1;i<=k+1;i++)
    {
        (res+=C[k+1][i]*B[k+1-i]%Mod*pw)%=Mod;
        if(i<=k)(pw*=n)%=Mod;
    }
    return res*inv[k+1]%Mod;
}
struct Hash
{
    size_t operator()(const array<long long,6>&a)const noexcept
    {
        size_t h=0;
        for(auto x:a)
            h^=hash<long long>{}(x+0x9e3779b97f4a7c15ULL+(h<<6)+(h>>2));
        return h;
    }
};
unordered_map<array<int,6>,int,Hash> mp;
inline int calc(int n,int a,int b,int c,int k1,int k2)
{
    if(!k2)return getpw(n,k1);
    if(!a)return ks(b/c,k2)*getpw(n,k1)%Mod;
    array<long long,6> key={n,a,b,c,k1,k2};
    if(mp.count(key))return mp[key];
    if(a>=c||b>=c)
    {
        int res=0;
        int nB=ks(b/c,Mod-2);
        int M=ks(b/c,k2);
        for(int x=0;x<=k2;x++)
        {
            int mA=1;
            int mB=M;
            for(int y=0;y<=k2-x;y++)
            {
                if(y!=k2-x)
                {
                    (res+=calc(n,a%c,b%c,c,k1+y,x)*mA%Mod*mB%Mod*ifac[x]%Mod*ifac[y]%Mod*ifac[k2-x-y])%=Mod;
                    (mA*=(a/c))%=Mod,(mB*=nB)%=Mod; 
                }
                else
                {
                    (res+=calc(n,a%c,b%c,c,k1+y,x)*mA%Mod*ifac[x]%Mod*ifac[y])%=Mod;
                }
            }
            if(x!=k2)(M*=nB)%=Mod;
        }
        return mp[key]=res*fac[k2]%Mod;
    }
    int m=(a*n+b)/c;
    int res=(getpw(m,k2-1)-(k2==1))*getpw(n,k1)%Mod;
    for(int i=0;i<=k2-1;i++)
    {
        for(int j=1;j<=k1+1;j++)
            res=(res-calc(m-1,c,c-b-1,a,i,j)*C[k2-1][i]%Mod*B[k1+1-j]%Mod*inv[k1+1]%Mod*C[k1+1][j]%Mod+Mod)%Mod;
        if(!k1)
            res=(res-C[k2-1][i]*getpw(m-1,i)%Mod+Mod)%Mod;
    }
    (res*=k2)%=Mod;
    for(int i=1;i<=k2-1;i++)
        res=(res+Mod-B[k2-i]*C[k2][i]%Mod*calc(n,a,b,c,k1,i)%Mod)%Mod;
    return mp[key]=res;
}
inline void solve()
{
    int n,a,b,c,k1,k2;
    cin>>n>>a>>b>>c>>k1>>k2;
    cout<<calc(n,a,b,c,k1,k2)<<'\n';
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    mp.reserve(1<<20);
    mp.max_load_factor(0.7);
    C[0][0]=1;
    for(int i=1;i<=21;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=21;j++)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%Mod;
    }
    B[0]=fac[0]=fac[1]=ifac[0]=ifac[1]=inv[0]=inv[1]=1;
    for(int i=2;i<=21;i++)
        fac[i]=fac[i-1]*i%Mod,
        ifac[i]=ks(fac[i],Mod-2),
        inv[i]=inv[Mod%i]*(Mod-Mod/i)%Mod;
    for(int i=1;i<=20;i++)
    {
        for(int j=0;j<i;j++)
            (B[i]+=B[j]*C[i+1][j])%=Mod;
        (B[i]*=inv[i+1])%=Mod;
        B[i]=(Mod-B[i])%Mod;
    }
    for(int i=1,opt=Mod-1;i<=20;i++,opt=Mod-opt)
        (B[i]*=opt)%=Mod;
    int T;cin>>T;
    while(T--)solve();
}

:::

part2 应用

显然但是,题目不会只是模板,需要转换。

Example1

link

给定 N,M,A,B ,求出:

\sum_{i=0}^{N}[(Ak+B) \bmod M>k]

:::success[solution] 先转换为 X_k \le k 的形式。

考虑利用 a \bmod b=a-b\lfloor \frac{a}{b} \rfloor,将原式变成 k(A-1)+B -1 < M \lfloor \frac{kA+B}{M} \rfloor

M 除过去,于是变成了 \lfloor \frac{k(A-1)+B-1}{M} \rfloor < \lfloor \frac{kA+B}{M} \rfloor,然后让左边 +1,变成 \lfloor \frac{k(A-1)+B-1+M}{M} \rfloor \le \lfloor \frac{kA+B}{M} \rfloor

注意到左式减右式一定 \le 1,于是答案就是 \displaystyle \sum_{i=0}^{N-1} \lfloor \frac{k(A-1)+B+M-1}{M} \rfloor-\lfloor \frac{kA+B}{M} \rfloor 了。

这是经典的类欧的形式,直接做就好了,注意特判 A=0 的情况,为 \min(n,B)。 :::

由上例可看出,可以将某种计数转化为两个式子相减为 1 的形式,可以使用类欧。

Example2

link

给定正整数 n,\,r ,求:

\sum_{d=1}^{n}(-1)^{\lfloor d\sqrt{r} \rfloor}

:::success[solution] 看到向下取整,想到 floor sum,但是指数太可怕了吧。

考虑 (-1)^x 的性质,有 (-1)^{2x}=1,可以得到 (-1)^x=1-2(x \bmod 2),化简一下可以得到:

(-1)^x=1-(x-2 \lfloor \frac{x}{2} \rfloor)=1-2x+4\lfloor \frac{x}{2} \rfloor

带入 x=\lfloor d\sqrt{r} \rfloor 可得:

\sum_{d=1}^{n}(-1)^{\lfloor d\sqrt{r} \rfloor}=\sum_{d=1}^{n}1-2\lfloor d\sqrt{r} \rfloor+4\lfloor \frac{\lfloor d\sqrt{r} \rfloor}{2} \rfloor

化简一下得到:

n-2\sum_{i=1}^{n} \lfloor i\sqrt{r} \rfloor+4\sum_{i=1}^{n} \lfloor \frac{ i\sqrt{r} }{2} \rfloor

接下来,令 \displaystyle f(N,a,b,c)=\sum_{i=1}^{N} \lfloor \frac{ a\sqrt{r}+b}{c} i\rfloor,于是答案就是 n-2f(N,1,0,1)+4f(N,1,0,2)

现在,开始推吧!

为了方便,令 t=\frac{ a \sqrt r+b}{c}

正常的类欧是取模,但这怎么取模?考虑按 t 的大小来分类。

考虑令 \delta=\lfloor t \rfloor,得到:

\lfloor ti\rfloor=\lfloor ti - \delta i +\delta i\rfloor=\lfloor (t-\delta)i \rfloor+\delta i

于是有:

f(N,a,b,c)=\frac{N(N+1)}{2}\delta+\sum_{i=1}^{N} \lfloor \frac{ a\sqrt{r}+b -c \delta}{c} i\rfloor =\frac{N(N+1)}{2}\delta+f(N,a,b-c\delta,c)

回忆之前的推导过程,我们令 M=\lfloor Nt \rfloor,带入可得:

f(N,a,b,c)=\sum_{i=1}^{N} \sum_{j=1}^{M} [j<ti]

因为此时 \lfloor it \rfloor 一定小于 it,所以这样写没有问题。

然后,推吧!

\begin{aligned} f(N,a,b,c)&=\sum_{i=1}^{N} \sum_{j=1}^{M} [j<ti]\\ &=\sum_{j=1}^{M}N-\lfloor \frac{j}{t} \rfloor\\ &=\sum_{j=1}^{M}N-\lfloor \frac{j}{\frac{a \sqrt r +b}{c}} \rfloor\\ &=\sum_{j=1}^{M}N-\lfloor \frac{jc}{a \sqrt r +b} \rfloor\\ &=\sum_{j=1}^{M}N-\lfloor j\frac{c(a\sqrt r-b)}{(a \sqrt r +b)(a\sqrt r-b)} \rfloor \\ &=NM-\sum_{j=1}^{M}\lfloor j\frac{ca\sqrt r-bc}{a^2r-b^2} \rfloor \\ &=NM-f(M,ca,-bc,a^2r-b^2) \end{aligned}

代码是简单的。

:::

Example3

有时会稍微考一考建模,比如下面这道题。

link

N 个编号为 1N 的盘子。第 i 个盘子上有 a_i 个石子。此外,还有一个空袋子。
你可以按照任意顺序、任意次数(包括 0 次)进行以下两种操作:

  • 从所有至少有 1 个石子的盘子中各取出 1 个石子,并将这些石子放入袋子中。
  • 从袋子中取出 N 个石子,并将每个盘子上各放 1 个石子。仅当袋子中至少有 N 个石子时,才能进行此操作。

操作结束后,第 i 个盘子上的石子数为 b_i。请计算所有可能的长度为 N 的整数序列 (b_1,\ b_2,\ \dots,\ b_N) 的种数,并对 998244353 取模。

:::success[sulotion] 考虑我们能够到达一个怎么样的局面。

注意到我们一定是先进行若干次第一种操作,再进行若干次第二种操作,故可以先枚举先经行了多少次第一种操作,成为步骤 A

注意到,如果两个步骤 AA',满足它们操作完的局面不同,那么容易证明,它们经过若干次全局加后的局面还是不同的,于是可以先把 a 排序,对于每个数它在前面的数全部减成 0 时被减了多少,可以得到式子:

a_1+1+ \sum_{i=2}^{n} \sum_{j=1}^{a_i-a_{i-1}} 1+\lfloor \frac{(n-i+1)(j+a_{i-1})+sum_{i-1}}{n} \rfloor

其中 sum 是前缀和,(n-i+1)(j+a_{i-1})+sum_{i-1} 就是一共放入袋子多少球。

然后 floor sum 即可。 :::

万能欧几里得

不用推那些屎式子,除了常数大几乎没有缺点了。

而且顾名思义,作用范围很更广,万能!

因为更抽象,所以更强。

我们用 (N,a,b,c) 表示一条线段 y=\frac{ax+b}{c}(0< x \le N),然后考虑一个由 U,R 构成的,长度为 N+\lfloor \frac{aN+b}{c} \rfloor 的字符串 S,满足有 NU\lfloor \frac{aN+b}{c} \rfloorR,第 iR 的前方有 \lfloor \frac{ai+b}{c} \rfloorU

几何解释就是从原点开始,每向右穿过一次竖向的网格线,就写下一个 R,每向上穿过一次横向的网格线,就写下一个 U

用一下 oiwiki 的图就是:

注意不能有格外的 U 且要在开始时写下 \lfloor \frac{b}{c} \rfloorU,并在经过整点时先写 U 再写 R

我们将这样的 S 成为操作序列。

那么这有什么用?

考虑将 U,R 视作某个幺半群(存在单位元的半群)内的的元素,然后将整个操作序列视为幺半群内元素的乘积,而问题最终的答案与这个乘积有关,接下来我们用 \circ 表示这个幺半群的运算,e 表示单位元。

还是有点抽象,就以经典的式子:

\sum_{i=0}^{N} i\lfloor \frac{ai+b}{c} \rfloor

为例吧。

初始有 cnt=0

每遇到一个 U,就 cnt\leftarrow cnt+1,遇到一个 R,就 ans\leftarrow cnt \times i,其中 i 是这是第几个 R,最后 ans 就是答案。

我们用 f(S) 来表示 S 答案,注意到有 f(S_1+S_2)=f(S_1) \circ f(S_2)

发现最终答案只 f(U),f(R) 有关,于是我们定义 F(n,a,b,c,f_1,f_2) 为求 \displaystyle \sum_{i=1}^{N} i\lfloor \frac{ai+b}{c} \rfloor,当前状态为 f_1f_2 时的答案,其中 f_1 是更新答案状态,f_2 是用来更新答案的状态。

我们就要来求 F(n,a,b,c,f(U),f(R))

首先,平移整格答案是不会变的,所以 b 可以任意对 c,考虑 ac 取模的影响,发现是每个 R 的前面多了 \lfloor \frac{a}{c} \rfloorU,所以可以得到:

F(N,a,b,c,f_1,f_2)=F(n,a \bmod c,b,c,f_1,f_1^{\lfloor \frac{a}{c} \rfloor} \circ f_2)

接下来,还是令 M=\lfloor \frac{an+b}{c} \rfloor

如果 M=0,则字符串全是 R 了,就直接就是 f_2^{N}

否则考虑翻转这条直线,让其以 y=x 对称过去,原来斜率 <1,翻转后自然就 >1 了,可以继续递归。

考虑翻转的影响,将翻转的直线分成三段,分别为 \le 1 的部分,\le M 的部分和 >M 的部分。

最后一段一定只有 $R$ 了,因为不可能再向上突破了,由于横着时定义域为 $[0,N]$,于换了后这个部分的值域就是 $N-\lfloor \frac{cM-b-1}{a} \rfloor$,于是就是 $f_2^{N-\lfloor \frac{cM-b-1}{a} \rfloor}$。 总结一下就是: $$ F(N,a,b,c,f_u,f_r)= \begin{cases} F(N,a,b \bmod c,c,f_u,f_r) &,b\ge c\\ F(N,a \bmod c,b,c,f_1,f_1^{\lfloor \frac{a}{c} \rfloor} \circ f_2) &,a\ge c\\ f_2^N &,\lfloor \frac{aN+b}{c}=0 \rfloor\\ f_2^{\lfloor \frac{c-b-1}{a} \rfloor} \circ f_1 \circ F(M-1,c,c-b-1,a,f_2,f_1) \circ f_2^{N-\lfloor \frac{cM-b-1}{a} \rfloor} &,\text{otherwise} \end{cases} $$ 注意初始要乘上 $f_{U}^{\lfloor \frac{b}{c} \rfloor}$。 那么用什么来表示 $f_U,f_R$ 呢? :::info[初步思考] 当然是我们万能的矩阵啦!~~毕竟也是除了常数大没有缺点嘛,和万欧超配的!~~ 考虑原本我们遍历一遍 $S$ 怎么算答案,显然就是维护一个答案向量 $A=[ans,cnt,cntR,cnt\times cntR,1]$ 初始为 $[0,0,1,0,1]$,然后按照遇到 $U,R$ 分类讨论写矩阵就好了,时间复杂度 $O(4^3 \log n)$。 最棒的的是,每道题这个板子全都适用!不用重推,只用改个矩阵! ::: 上述方法显然有个问题,如果是你谷板子题,要 $8 \times 8$ 的矩阵,loj 的板子要更多!直接用矩阵会常数上天,复杂度爆炸!怎么办? 当然可以猛卡常,但没什么意义,考虑用更聪明的实现。 直接维护 $\sum y,\sum xy,\sum y^2,x,y$,那么考虑贡献的合并。 那么合并 $S_1,S_2$ 时有:$x=x_1+x_2,y=y_1+y_2$. 然后有: $$ \begin{aligned} \sum y&=\sum_{S_1} y+\sum_{S_2} (y+y_1)\\ \sum xy&=\sum_{S_1}yx+\sum_{S_2}(y+y_1)(x+x_1)\\ \sum y^2&=\sum_{S_1}y^2+\sum_{S_2}(y+y_1)^2\\ \end{aligned} $$ 然后直接做就好了,可以看见式子简洁的不是一星半点。 最后给出锣鼓板子的实现: :::success[code] ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int Mod=998244353; struct Node{int f,g,h,u,r;}E,fu,fr; inline Node operator *(Node A,Node B) { Node res; res.u=(A.u+B.u)%Mod; res.r=(A.r+B.r)%Mod; res.f=(A.u*B.r+A.f+B.f)%Mod; res.g=(A.r*B.f+B.r*(B.r-1)/2%Mod*A.u+B.r*A.r%Mod*A.u+A.g+B.g)%Mod; res.h=(2*A.u*B.f+A.u*A.u%Mod*B.r+A.h+B.h)%Mod; return res; } inline Node ks(Node a,int b) { Node res=E; while(b) { if(b&1)res=res*a; a=a*a;b>>=1; } return res; } Node asgcd(int n,int a,int b,int c,Node f1,Node f2) { b%=c; if(a>=c)return asgcd(n,a%c,b,c,f1,ks(f1,a/c)*f2); int m=(a*n+b)/c; if(!m)return ks(f2,n); return ks(f2,(c-b-1)/a)*f1*asgcd(m-1,c,c-b-1,a,f2,f1)*ks(f2,n-(c*m-b-1)/a); } inline void solve() { int n,a,b,c; cin>>n>>a>>b>>c; auto ans=ks(fu,b/c)*fr*asgcd(n,a,b,c,fu,fr); cout<<ans.f<<' '<<ans.h<<' '<<ans.g<<'\n'; } signed main() { E={0,0,0,0,0}; fu={0,0,0,1,0}; fr={0,0,0,0,1}; ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); int T;cin>>T; while(T--)solve(); } ``` :::