这个板子是真(TM)毒。心脑血管疾病患者慎入
总之先来康柿子,给定非负整数(n,a,b,c),求下面三个柿子(这里的(g,h)顺序好像和题目颠倒了......问题不大):
并输出对(998244353)取模的结果,保证(0 le n,a,b,c le 10^9, c ot= 0)。
(f(a,b,c,n)):
由于(a)非负,所以(leftlfloor frac{ai + b}{c} ight floor)对于(i)是递增的,为了方便,下面令(d(i) = leftlfloor frac{ai + b}{c} ight floor)。
换句话说,(d(i))的最大值就是(d(n)),我们令(m = d(n))。
那么就有
令(j = j-1)(为什么呢?等会就知道了。)
然后把取整符号去掉
我们还能进一步化简中括号里的柿子
这里直接两边除以(a)?然而并不能。因为除以(a)后我们肯定会再把取整符号填上去,但(frac{cj + c - b}{a} le i)并不能说明(leftlfloor frac{cj + c - b}{a} ight floor le i)。
所以我们还是要把两边减一再变
再带回(d(i)),同时令(alpha = alpha(j) = leftlfloor frac{cj + c - b - 1}{a} ight floor)
回到原式
交换求和顺序再来颓柿子
后面的(sumlimits_{j = 0}^{m - 1} alpha)也就是(f(c, c -b - 1, a, m - 1)),于是(上面让(j)从(0)开始就是为了这一步)
没了?并没有......
发现如果(c - b - 1 < 0)的话这个柿子就炸了......
考虑让(b)对(c)取模,顺便也把(a)带走((a = 0)也是边界)。
即
注意到(leftlfloor frac{a}{c} ight floor c i)和(leftlfloor frac{b}{c} ight floor c)都是整数,且有约数(c),直接提出来
于是
这样就可以递归算了,边界就是(a = 0)
时间复杂度:
观察(a)位置上和(c)位置上的数,第一次是((a,c))(不放设(a < c)),第(2)次会变为((c,a)),第三次((c mod a, a))然后再交换位置,再取模。这不就是(gcd)吗?于是复杂度就是(gcd)的复杂度(*2)。
(g(a,b,c,n))
类似上面的(f),我们也照这个样子推导(g)。(一些定义和上面类似)。
当(a ge c or b ge c)时
把(d(i))展开
当(a < c and b < c)时
第二个(sum)就是对(alpha+1...n)这些整数求和,直接套公式
这里用到了(f)和(h)。
边界就是(a = 0)时,(g = frac{1}{2}n(n+1)leftlfloor frac{b}{c} ight floor)
发现(g)也是一个类似于(f)的递归过程,可以和(f)放到一起算。
(h(a,b,c,n))
感觉平方项很难通过枚举值算贡献的方法化简......
怎么办呢?
考虑到(1+2+...+n = frac{1}{2}n(n+1)),那么(n^2 = 2left(sumlimits_{k = 0}^{n}k ight) - n)
带到(d(i))里再带回原柿就是
考虑怎么求(sumlimits_{i = 0} ^ n sumlimits_{k = 0} ^ {d(i)}k),同样的,让(j)代替(k),并从(0)跑到(m-1),然后交换求和顺序
中括号里我们已经化简过了,再把(j+1)提到外面
所以(h(a,b,c,n) = m(m+1)n - 2g(c,c-b-1,a,m-1) - 2f(c,c-b-1,a,m-1) - f(a,b,c,n))
取模的话暴力三元完平展开就好了
边界就是(a = 0),(h(a,b,c,n) = leftlfloor frac{b}{c} ight floor ^ 2(n+1))。(h)也可以和(f,g)放到一起算。
总复杂度是(gcd)的复杂度。
(Code):
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
char ch; ll x = 0; int f = 1; while (!isdigit(ch = getchar())) f = ch=='-' ? -f : f;
while (isdigit(ch)){ x = x*10 + ch - '0'; ch = getchar(); } return x * f;
}
const int P = 998244353, i2 = 499122177, i6 = 166374059;
void update(ll &x, ll y) {
x += y; if (x >= P) x -= P;
}
inline ll s1(ll n) {
n %= P; return n % P * (n+1) % P * i2 % P;
}
inline ll s2(ll n) {
n %= P; return n % P * (n+1) % P * (2ll*n%P + 1) % P * i6 % P;
}
inline ll sqr(ll x) {
return 1ll * x * x % P;
}
#define chk(x) assert(0<=(x) && (x)<P)
void euclid(ll a, ll b, ll c, ll n, ll &f, ll &g, ll &h) {
ll ac = a/c, bc = b/c, m = 1ll * (1ll*a*n + b) / c;
if (a == 0) {
f = 1ll * bc * (n + 1) % P, g = 1ll * bc * s1(n) % P;
h = 1ll * sqr(bc) * (n + 1) % P;
return;
}
ll f1, g1, h1;
if (a >= c || b >= c) {
euclid(a % c, b % c, c, n, f1, g1, h1);
f = g = h = 0;
update(f, 1ll * s1(n) * ac % P), update(f, 1ll * bc * (n+1) % P), update(f, f1);
update(g, 1ll * s2(n) * ac % P), update(g, 1ll * s1(n) * bc % P), update(g, g1);
update(h, 1ll * s2(n) * sqr(ac) % P), update(h, 1ll * (n+1) * sqr(bc) % P);
update(h, h1), update(h, 2ll * ac * g1 % P);
update(h, 1ll * n * (n+1) % P * ac % P * bc % P);
update(h, 2ll * bc * f1 % P);
return;
}
euclid(c, c-b-1, a, m-1, f1, g1, h1);
f = g = h = 0;
update(f, 1ll * n * m % P), update(f, P - 1ll * f1 % P);
update(g, 1ll * n % P * (n+1) % P * m % P);
update(g, P - 1ll * f1 % P), update(g, P - 1ll * h1 % P), g = g * i2 % P;
update(h, 1ll * m * (m+1) % P * n % P), update(h, P - 2ll * g1 % P);
update(h, P - 2ll * f1 % P), update(h, P - f);
}
int main(){
for (int T = read(); T; T--) {
ll n = read(), a = read(), b = read(), c = read(), f, g, h;
euclid(a, b, c, n, f, g, h);
printf("%lld %lld %lld
", f, h, g);
}
return 0;
}
一般形式的话......估计这辈子都学不动了(QwQ)