P3321 [SDOI2015]序列统计(离散对数下NTT,乘法换加法)
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3321 [SDOI2015]序列统计(离散对数下NTT,乘法换加法)相关的知识,希望对你有一定的参考价值。
整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
Weblink
https://www.luogu.com.cn/problem/P3321
Problem
Solution
关于离散对数的概念及其应用建议看我的《算法竞赛中的初等数论》 中的 0x61.3 整数的指标(也称指数、离散对数),哦还没更新到这儿啊,那没事了
题解建议看这里:https://www.luogu.com.cn/blog/ZigZagKmp/solution-p3321(借用离散对数将乘法转换成加法之后就是一个经典的计数问题了,非常简单。他写的实在是太好了,然后因为后天就要考毛概了我还没开始复习,题解我就懒得写了… )
不过他是先倍增预处理然后再将 n n n 二进制拆分,再组成 n n n 的方式,其实直接快速幂即可,一样是 O ( l o g n ) O(logn) O(logn) 常数会更小一点,还好写一些。
Code
// Problem: P3321 [SDOI2015]序列统计
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3321
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;
const int N = 2e6 + 7, mod = 1004535809, G = 3;
ll a[N], b[N], h[N];
int n, m, X, S, GG;
int limit, RR[N], L;
ll g[N], f[N], dtol[N], ltod[N];
vector<int>factor;
int qpow(int a, int b, int mod = 1004535809)
{
int res = 1;
while(b){
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
vector<int> get_factor(ll n)
{
vector<int>res;
for(int i = 1; i * i <= n; ++ i) {
if(n % i == 0) {
res.push_back(i);
if(i != n / i) {
res.push_back(n / i);
}
}
}
return res;
}
void get_RT(int n)
{
ll phi_m = n - 1;
factor = get_factor(phi_m);
for(int i = 2; i < n; ++ i) {
bool flag = 1;
for(int j = 0; j < (int)factor.size(); ++ j) {
if(factor[j] != phi_m && qpow(i, factor[j], n) == 1) {
flag = 0;
break;
}
}
if(flag) {
GG = i;
break;
}
}
int tmp = 1;
for(int i = 0; i < phi_m; ++ i, tmp = 1ll * tmp * GG % n)
dtol[tmp] = i, ltod[i] = tmp;
return ;
}
ll inv(ll x) {return qpow(x, mod - 2);}
void NTT(ll *A, int type = 1)
{
for(int i = 0; i < limit; ++ i)
if(i < RR[i])
swap(A[i], A[RR[i]]);
for(int mid = 1; mid < limit; mid <<= 1) {
ll wn = qpow(G, (mod - 1) / (mid * 2));
if(type == -1) wn = qpow(wn, mod - 2);
for(int len = mid << 1, pos = 0; pos < limit; pos += len) {
ll w = 1;
for(int k = 0; k < mid; ++ k, w = (w * wn) % mod) {
int x = A[pos + k], y = w * A[pos + mid + k] % mod;
A[pos + k] = (x + y) % mod;
A[pos + k + mid] = (x - y + mod) % mod;
}
}
}
if(type == -1) {
ll limit_inv = inv(limit);
for(int i = 0; i < limit; ++ i)
A[i] = (A[i] * limit_inv) % mod;
}
}
void mul(ll *f, ll *g, ll *ans, int n, int m, int mod_len)
{
limit = 1, L = 0;
while(limit < n + m - 1) limit <<= 1, ++ L;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
for(int i = 0; i < n; ++ i)
a[i] = f[i];
for(int i = n; i < limit; ++ i)
a[i] = 0;
for(int i = 0; i < m; ++ i)
b[i] = g[i];
for(int i = m; i < limit; ++ i)
b[i] = 0;
NTT(a), NTT(b);
for(int i = 0; i < limit; ++ i) {
a[i] = 1ll * a[i] * b[i] % mod;
}
NTT(a, -1);
for(int i = 0; i < mod_len; ++ i)
ans[i] = a[i];
for(int i = mod_len; i < limit; ++ i)
ans[i % mod_len] = (ans[i % mod_len] + a[i]) % mod;
for(int i = mod_len; i < limit; ++ i)
ans[i] = 0;
}
signed main()
{
scanf("%lld%lld%lld%lld", &n, &m, &X, &S);
get_RT(m);
for(int i = 1; i <= S; ++ i) {
int x;
scanf("%lld", &x);
if(x != 0) {
f[dtol[x]] ++ ;
}
}
g[0] = 1;
while(n) {
if(n & 1) mul(f, g, g, m - 1, m - 1, m - 1);
mul(f, f, f, m - 1, m - 1, m - 1);
n >>= 1;
}
printf("%lld\\n", g[dtol[X]]);
return 0;
}
以上是关于P3321 [SDOI2015]序列统计(离散对数下NTT,乘法换加法)的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 3992: [SDOI2015]序列统计 [快速数论变换 生成函数 离散对数]