「学习笔记」单位根反演

单位根反演

公式推导

[nk]=1ni=0n1ωnik

证明:

nk 时,ωnik=1,显然成立;

nk 时,对右边项进行等比数列求和得到 1nωnnk1ωnk1=0

单位根反演常用于求某个多项式特定倍数的系数和。

i=0nk[xik]f(x)=i=0n[ki][xi]f(x)=i=0n[xi]f(x)1kj=0k1ωkji=1ki=0naij=0k1ωkij=1ki=0nj=0k1ai(ωkj)i=1kj=0k1f(ωkj)

同理可推出

i=0nk[xik+r]f(x)=1ki=0k1f(ωki)ωkir

例题

[LOJ6485] LJJ 

[i=0n((ni)siaimod4)]mod998244353
1T105,1n1018,1s,a0,a1,a2,a3108

i=0n((ni)siaimod4)=j=03i=0n(ni)siaj[4|ij]=j=03i=0n(ni)siaj14k=03ω4k(ij)=14k=03j=03ajω4jki=0n(ni)siω4ik=14k=03j=03ajω4jk(sω4k+1)n

直接计算即可,时间复杂度 O(logn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
int T, s, ans, a[5]; ll n;
inline ll qpow(ll a, ll b) {
	ll ans = 1;
	for (; b; b >>= 1, a = a * a % mod) if (b & 1) ans = ans * a % mod;
	return ans;
}
const int omega = qpow(3, (mod - 1) / 4);
const int omega_ = qpow(omega, mod - 2);
const int inv4 = qpow(4, mod - 2);
int main() {
	scanf("%d", &T);
	while (T --) {
		int ans = 0;
		scanf("%lld%d", &n, &s);
		for (int i = 0; i < 4; ++ i) scanf("%d", a + i);
		for (int k = 0; k < 4; ++ k)
			for (int j = 0; j < 4; ++ j)
				(ans += a[j] * qpow(omega_, j * k) % mod * (qpow((s * qpow(omega, k) + 1) % mod, n)) % mod) %= mod;
		printf("%d\n", (ll)ans * inv4 % mod);
	}
	return 0;
}

[AtCoder Regular Contest 145 F - Modulo Sum of Increasing Sequences]

对于每个 K=0,1,,MOD1,计数长度为 N,值域为 [0,M],总和对 MOD 取模后结果为 K 的不降序列 A 的数量,答案对 998244353 取模。

1N,M106,1MOD500

bi=ai+i1,则有 i<n,bi<bi+1。问题转化成了在 [0,M+N1] 中选取 N 个数使得总和对 MOD 取模后结果为 K。下令 n=M+N。为了方便,记 p=MOD

先不考虑长度的限制,列出 OGF

F(x)=i=0n1(1+xi)

答案即为

1pi=0p1F(ωpi)ωpik=1pi=0p1j=0n1(1+ωpij)ωpik

Special Case: pn

假设 pn,记 d=gcd(p,i),则

1pi=0p1j=0n1(1+ωpij)ωpik=1pi=0p1(j=0pd1(1+ωpdij)d)npωpik

用分圆多项式作简化

xn1=i=0n1(xωni)

带入 x=1,得

(1)n1=(1)ni=0n1(1+ωni)

所以

i=0n1(1+ωni)=1(1)n

那么

1pi=0p1(j=0pd1(1+ωpdij)d)npωpik=1pi=0p1(j=0pd1(1+ωpdij))dnpωpik=1pi=0p1(1(1)pd)dnpωpik=1pdp(1(1)pd)dnp(gcd(i,p)=dωpik)=1pdp(1(1)pd)dnp(gcd(i,pd)=1ωpdik)=1pdp(1(1)pd)dnp(gcd(i,pd)=1ωpdik)=1pdp(1(1)pd)dnp(jpdμ(j)i=0pdj1ωpdjik)=1pdp(1(1)pd)dnp(jpdμ(j)[pdjk]pdj)

现在加上长度的限制,在初始的 OGF 中多加一个元 y,变为

i=0n1(1+xiy)

推导后的结果为

[yN]1pdp(1(y)pd)dnp(jpdμ(j)[pdjk]pdj)

可以用二项式定理 O(p2) 求出。


General Case: pn

根据上面的推导,可以 O(p2) 求出 pn 的情况,我们在此基础上做 pn 的情况。

首先令 n=nppO(p2) 求出 0n1 的答案。

考虑添加进 nn1 之间的数,也就是求出在 nn1 之间选取 i 个数满足这些数的和 modp=j 的方案数,最后再和之前的答案进行合并。

因为 nn<p,所以这部分直接用 dp 解决,时间复杂度为 O(p3)

至此,本题解决。总时间复杂度 O(N+M+p3)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 505, M = 2e6 + 5, mod = 998244353;
int n, m, p, _n, mu[N], ans[N], dp[N][N];
ll fac[M], inv[M], Inv[M];
vector<int> divisors[N];
vector<pair<int, vector<int>>> gr;
inline void precalc(int n) {
	fac[0] = inv[0] = Inv[0] = fac[1] = inv[1] = Inv[1] = 1;
	for (int i = 2; i < n; ++ i) {
		fac[i] = fac[i - 1] * i % mod;
		Inv[i] = (mod - mod / i) * Inv[mod % i] % mod;
		inv[i] = inv[i - 1] * Inv[i] % mod; 
	}
}
inline ll C(int n, int m) {
	if (n < m || n < 0 || m < 0) return 0;
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
	precalc(M);
	scanf("%d%d%d", &n, &m, &p), n += m, m = n - m, _n = n / p * p;
	dp[0][0] = 1;
	for (int i = 1; i <= n - _n; ++ i)
	        for (int k = i; k; -- k)
			for (int j = 0; j < p; ++ j)
				(dp[k][j] += dp[k - 1][(j - (i + _n - 1) % p + p) % p]) %= mod;
	for (int i = 1; i <= p; ++ i)
		for (int j = i; j <= p; j += i)
			divisors[j].emplace_back(i);
	mu[1] = 1;
	for (int i = 1; i <= p; ++ i)
		for (int j = i + i; j <= p; j += i)
			mu[j] -= mu[i];
	for (int i = 2; i <= p; ++ i) (mu[i] += mod) %= mod;
	for (auto d : divisors[p]) {
		vector<int> c(p);
		for (int k = 0; k < p; ++ k)
			for (auto j : divisors[p / d])
				if ((k * d * j) % p == 0)
					(c[k] += (ll)p / d / j * mu[j] % mod) %= mod;
		gr.emplace_back(d, c);
	}
	for (int use = 0; use <= min(n - _n, m); ++ use) {
		int leave = m - use;
		vector<int> coef(p);
		for (auto &&[d, c] : gr) {
			int b = p / d; ll co;
			if (leave % b > 0) continue;
			if (b & 1) co = Inv[p];
			else co = (leave / b) & 1 ? mod - Inv[p] : Inv[p];
			(co *= C(d * _n / p, leave / b)) %= mod;
			for (int i = 0; i < p; ++ i)
				(coef[i] += co * c[i] % mod) %= mod;
		}
		for (int i = 0; i < p; ++ i)
			for (int j = 0; j < p; ++ j)
				(ans[(i + j) % p] += (ll)coef[i] * dp[use][j] % mod) %= mod;
	}
	int move = (m - 1LL) * m / 2 % p;
	for (int i = 0; i < p; ++ i) printf("%d ", ans[(i + move) % p]);
	return 0;
}
posted @   Samsara-soul  阅读(802)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示