根号分治 + NTT优化dp转移:ICPC2026网络赛B

考虑一个朴素的dp
定义 ci 表示有多少个 a 小于等于 i。显然 i 只有模这 ci 个数才会改变。
设 fi 表示在整个过程中,有多少种排列使得 i 存在过。显然对于 i<a1,fi 就是答案。而 i≥a1,答案是0。
fi×ci1→j∈{a}fimodj
1 2 3 4 5 6
| f[m] = fac[n]; for(v = m / 2; v >= 0; --v) { for(i = c[v] + 1; i <= n; ++i) for(u = a[i] + v; u <= m; u += a[i]) Add(f[v], f[u] * inv[c[u]]); }
|
我们发现一个性质。imodj 只要能成功取模,必然小于 2i。因此这启示我们可以进行分块处理,按 B 分块后,除了最后一块,其他不可能存在内部之间的转移。
我们考虑现在对于一个块 y∈[l,r] 内的转移,我们只考虑从 >r 的位置 u 转移过来。接下来我们考虑模数 x>r。必然满足 u=kx+y,即 x∣u−y。令 gi=ci!fi,dbas=#{a[x]∣bas}
fy=∑gudu−y
右边这个东西就是一个卷积的形式,直接NTT即可。这部分的复杂度是 O(Bnlogn)
然后考虑模数 x∈[l,r],在这种情况下直接暴力枚举即可,复杂度 O(B3)
同样,最后一个块内也可以直接暴力枚举。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| signed main() { #ifdef LOCAL freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif
n = read(); m = read(); int B = 4096; init(max(n, m)); for(i = 1; i <= n; ++i) a[i] = read(), mp[a[i]] = 1; for(i = j = 0; i <= m; ++i) { while(j < n && a[j + 1] <= i) ++j; c[i] = j; } f[m] = fac[n]; g[m] = f[m] * inv[c[m]] % mo; if(a[1] > m) { for(i = 0; i <= m; ++i) printf("%lld ", f[i]); return 0; } for(i = 1; i <= n; ++i) if(a[i] <= m) Add(f[m % a[i]], g[m]); r = m / 2; l = (m / 2) / B * B; prex = m; int M = m / 2; while(r >= 0) {
for(x = r + 1; x <= prex; ++x) if(mp[x]) { for(bas = x; bas <= m; bas += x) d[bas]++; } vector<int>G(M - r + 1), D(m - r + 1); for(i = r + 1; i <= M; ++i) G[M - i] = g[i]; for(i = r + 1; i <= m; ++i) D[i - r] = d[i]; auto H = atcoder :: convolution(G, D); for(y = l; y <= r; ++y) if(y + r <= M) Add(f[y], H[M - y - r]); for(y = l; y <= r; ++y) { for(i = c[y] + 1; i <= n && a[i] <= r; ++i) { for(x = a[i], u = x + y; u <= M; u += x) Add(f[y], g[u]); }
g[y] = f[y] * inv[c[y]] % mo; } prex = r; r = l - 1; l -= B; } for(v = prex; v >= 0; --v) { for(i = c[v] + 1; i <= n && a[i] + v <= prex; ++i) for(u = a[i] + v; u <= prex; u += a[i]) Add(f[v], g[u]); g[v] = f[v] * inv[c[v]] % mo; } for(i = 0; i < a[1]; ++i) printf("%lld ", f[i]); for(; i <= m; ++i) printf("0 "); return 0; }
|