帮直线数一数有多少点被它压在身下吧~
cheng2010
·
·
算法·理论
有时候直线在数轴上扑到了很多点,但太多了~
而我们又想知道,它吧多少点压住了,怎么办呢~
类欧几里得
学了这个其实就差不多了。
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 个编号为 1 到 N 的盘子。第 i 个盘子上有 a_i 个石子。此外,还有一个空袋子。
你可以按照任意顺序、任意次数(包括 0 次)进行以下两种操作:
- 从所有至少有 1 个石子的盘子中各取出 1 个石子,并将这些石子放入袋子中。
- 从袋子中取出 N 个石子,并将每个盘子上各放 1 个石子。仅当袋子中至少有 N 个石子时,才能进行此操作。
操作结束后,第 i 个盘子上的石子数为 b_i。请计算所有可能的长度为 N 的整数序列 (b_1,\ b_2,\ \dots,\ b_N) 的种数,并对 998244353 取模。
:::success[sulotion]
考虑我们能够到达一个怎么样的局面。
注意到我们一定是先进行若干次第一种操作,再进行若干次第二种操作,故可以先枚举先经行了多少次第一种操作,成为步骤 A。
注意到,如果两个步骤 A 和 A',满足它们操作完的局面不同,那么容易证明,它们经过若干次全局加后的局面还是不同的,于是可以先把 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,满足有 N 个 U 和 \lfloor \frac{aN+b}{c} \rfloor 个 R,第 i 个 R 的前方有 \lfloor \frac{ai+b}{c} \rfloor 个 U。
几何解释就是从原点开始,每向右穿过一次竖向的网格线,就写下一个 R,每向上穿过一次横向的网格线,就写下一个 U。
用一下 oiwiki 的图就是:
注意不能有格外的 U 且要在开始时写下 \lfloor \frac{b}{c} \rfloor 个 U,并在经过整点时先写 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_1,f_2 时的答案,其中 f_1 是更新答案状态,f_2 是用来更新答案的状态。
我们就要来求 F(n,a,b,c,f(U),f(R))。
首先,平移整格答案是不会变的,所以 b 可以任意对 c,考虑 a 对 c 取模的影响,发现是每个 R 的前面多了 \lfloor \frac{a}{c} \rfloor 的 U,所以可以得到:
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();
}
```
:::