多项式泛做1
2021 icpc Shanghai B
题意:给定一个长度为\(n\)的排列\(P\),要求计算出有多少种长度也为\(n\)的排列\(Q\)满足\(\forall i\in\{1,2,...,n-1\}\),\(Q_{i+1}\neq P_{Q_i}\)。最后答案对\(998244353\)取模。
Sol:对于序列\(P\),先找出有多少个环并且计算环的大小,比如现在序列\(P=\{3,4,1,2\}\),根据下标的对应关系构造环,\(P[1]=3\),\(1\)和\(3\)连边有向边,然后找\(P[3]\),发现\(P[3]=1\),所以\(1,3\)在一个环中,同理\(4,2\)也在一个环中。假设我们现在有\(cnt\)个环,每个环的大小是\(sz_i\),再来看问题要求\(O_{i+1}\neq P_{Q_i}\),可以发现最后构造的\(Q\)相邻的两个数\(Q_i\)和\(Q_{i+1}\)不能有有向边直接相连,这里的有向边的方向可以看做序列中的顺序,可以手动模拟一下。进一步把选点问题变成选边问题,考虑容斥,最后问题就可以转化成不能在环中出现的边的方案,考虑容斥,转为为至少选\(0\)条边,那么根据二项式反演\(f(0)=\sum_{i=0}^{n}(-1)^{i}g(i)\),其中\(g(i)\)为至少选\(i\)条在环中的边,构造生成函数\(F(x)=(1+C_{sz_1}^1x+...+C_{sz_1}^{sz_1-1}x^{sz_1-1})...(1+C_{sz_{cnt}}^1x+...+C_{sz_{cnt}}^{sz_{cnt}-1}x^{sz_{cnt}-1})\) ,那么\(g(i)=(n-i)![x^i]F(x)\),最后乘个\((n-i)!\)可以看做钦定\(i\)条必须选,剩下\((n-i)\)条随便选,所以乘个\((n-i)!\)
然后\(F(x)\)用分治求即可。
//#pragma GCC optimize(2)
#include
#define ll long long
#define pb push_back
using namespace std;
const int N = 2e5+10;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809
const int M=1e6+10;
const int mod=998244353;
template void rd(T &x)
{
x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
vectorC[N];
namespace Poly
{
#define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
#define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了
typedef vector poly;
const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
//一般模数的原根为 2 3 5 7 10 6
const int inv_G = qpow(G, mod - 2);
int RR[N], deer[2][19][N], inv[N];
void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
for(int p = 1; p <= t; ++ p) {
int buf1 = qpow(G, (mod - 1) / (1 << p));
int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
deer[0][p][0] = deer[1][p][0] = 1;
for(int i = 1; i < (1 << p); ++ i) {
deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
}
}
inv[1] = 1;
for(int i = 2; i <= (1 << t); ++ i)
inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
}
int NTT_init(int n) {//快速数论变换预处理
int limit = 1, L = 0;
while(limit <= n) limit <<= 1, L ++ ;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
return limit;
}
void NTT(poly &A, int type, int limit) {//快速数论变换
A.resize(limit);
for(int i = 0; i < limit; ++ i)
if(i < RR[i])
swap(A[i], A[RR[i]]);
for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
int len = mid >> 1;
for(int pos = 0; pos < limit; pos += mid) {
int *wn = deer[type][j];
for(int i = pos; i < pos + len; ++ i, ++ wn) {
int tmp = 1ll * (*wn) * A[i + len] % mod;
A[i + len] = ck(A[i] - tmp + mod);
A[i] = ck(A[i] + tmp);
}
}
}
if(type == 0) {
for(int i = 0; i < limit; ++ i)
A[i] = 1ll * A[i] * inv[limit] % mod;
}
}
inline poly poly_mul(poly A, poly B) {//多项式乘法
int deg = A.size() + B.size() - 1;
int limit = NTT_init(deg);
poly C(limit);
NTT(A, 1, limit);
NTT(B, 1, limit);
for(int i = 0; i < limit; ++ i)
C[i] = 1ll * A[i] * B[i] % mod;
NTT(C, 0, limit);
C.resize(deg);
return C;
}
//多个多项式相乘CDQ或者利用优先队列启发式合并
inline poly CDQ(int l,int r)
{
if(l==r)
{
return C[l];
}
int mid=l+r>>1;
poly L=CDQ(l,mid);
poly R=CDQ(mid+1,r);
return poly_mul(L,R);
}
}
int n, k;
int P[N];
ll f[N],inf[N],xs[N];
int cnt,cir[N],cek[N];
inline ll cal(int n,int m)
{
return f[n]*inf[m]%mod*inf[n-m]%mod;
}
int main()
{
//freopen("in.in","r",stdin);
Poly::init(18);//2^21 = 2,097,152,根据题目数据多项式项数的大小自由调整,注意大小需要跟deer数组同步(21+1=22)
int n;
rd(n);
f[0]=inf[0]=1;
for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod;
inf[n]=qpow(f[n],mod-2);
for(int i=n-1;i>=1;i--) inf[i]=inf[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++) rd(P[i]);
for(int i=1;i<=n;i++)
{
if(!cek[i])
{
int sz=0;
cnt++;
int u=i;
while(!cek[u]) ++sz,cek[u]=1,u=P[u];
cir[cnt]=sz;
}
}
sort(cir+1,cir+1+cnt);
for(int i=1;i<=cnt;i++)
for(int j=0;j
CF 1218 E. Product Tuples(分治NTT,生成函数)
题意:
给定一个长度为\(n\)的序列\(A\)和一个整数\(k(1\le k \le n)\),定义\(k\)元组(即元素个数为\(k\)的集合)的贡献为这个\(k\)元组里面所有数的乘积,求\(A\)的所有可能形成的\(k\)元组((即元素个数为\(k\)的子集)的贡献和。\(\sum\prod a_{p1}a_{p2}...a_{pk}\)
题解:
有点类似将一个数分解素数分解后求它的约数个数和的思想。用到生成函数,答案其实就是求\(\sum_{i=1}^n(1+a_ix)\),最后的答案就是\(x^k\)的系数。如果暴力用求的话要进行\(n-1\)次\(NTT\),时间复杂度约为\(O(n^2logn)\),显然不行,所以用分治\(NTT\),时间复杂度将为\(O(nlog^2n)\)。
扩展:
其实这里就是多个多项式相乘,有两种思路,一种是上述的\(CDQ分治\),另一种是启发式合并,维护一个小根堆,每次把度数小的两个多项式做乘法(好像是,还不确定,有待考察)
Code
//#pragma GCC optimize(2)
#include
using namespace std;
typedef long long ll;
const int N = 4e5+10;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;
const int mod = 998244353;
const int M=2e4+10;
ll a[M],b[M];
template void read(T &x)
{
x = 0;
register int f = 1;
register char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
int qpow(int a, int b)
{
int res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly
{
#define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
#define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了
typedef vector poly;
const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
//一般模数的原根为 2 3 5 7 10 6
const int inv_G = qpow(G, mod - 2);
int RR[N], deer[2][19][N], inv[N];
void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
for(int p = 1; p <= t; ++ p) {
int buf1 = qpow(G, (mod - 1) / (1 << p));
int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
deer[0][p][0] = deer[1][p][0] = 1;
for(int i = 1; i < (1 << p); ++ i) {
deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
}
}
inv[1] = 1;
for(int i = 2; i <= (1 << t); ++ i)
inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
}
int NTT_init(int n) {//快速数论变换预处理
int limit = 1, L = 0;
while(limit <= n) limit <<= 1, L ++ ;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
return limit;
}
void NTT(poly &A, int type, int limit) {//快速数论变换
A.resize(limit);
for(int i = 0; i < limit; ++ i)
if(i < RR[i])
swap(A[i], A[RR[i]]);
for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
int len = mid >> 1;
for(int pos = 0; pos < limit; pos += mid) {
int *wn = deer[type][j];
for(int i = pos; i < pos + len; ++ i, ++ wn) {
int tmp = 1ll * (*wn) * A[i + len] % mod;
A[i + len] = ck(A[i] - tmp + mod);
A[i] = ck(A[i] + tmp);
}
}
}
if(type == 0) {
for(int i = 0; i < limit; ++ i)
A[i] = 1ll * A[i] * inv[limit] % mod;
}
}
poly poly_mul(poly A, poly B) {//多项式乘法
int deg = A.size() + B.size() - 1;
int limit = NTT_init(deg);
poly C(limit);
NTT(A, 1, limit);
NTT(B, 1, limit);
for(int i = 0; i < limit; ++ i)
C[i] = 1ll * A[i] * B[i] % mod;
NTT(C, 0, limit);
C.resize(deg);
return C;
}
//多个多项式相乘CDQ或者利用优先队列启发式合并
poly CDQ(int l,int r)
{
if(l==r)
{
poly res(2);
res[0]=1;
res[1]=b[l];
return res;
}
int mid=l+r>>1;
poly L=CDQ(l,mid);
poly R=CDQ(mid+1,r);
return poly_mul(L,R);
}
//多项式牛顿迭代:求g(f(x))=0mod(x^n)中的f(x)
}
int n, k;
int main()
{
Poly::init(18);//2^21 = 2,097,152,根据题目数据多项式项数的大小自由调整,注意大小需要跟deer数组同步(21+1=22)
read(n);
read(k);
for(int i=1;i<=n;i++) read(a[i]);
int T;
read(T);
while(T--)
{
int type,q,id,d,L,R;
read(type);
if(type==1)
{
read(q),read(id),read(d);
for(int i=1;i<=n;i++)
if(i==id) b[i]=(q-d+mod)%mod;
else b[i]=(q-a[i]+mod)%mod;
}
else
{
read(q),read(L),read(R),read(d);
for(int i=1;i<=n;i++)
if(L<=i&&i<=R) b[i]=(q-(a[i]+d)+mod)%mod;
else b[i]=(q-a[i]+mod)%mod;
}
poly res=Poly::CDQ(1,n);
printf("%lld\n",(1LL*res[k]+mod)%mod);
}
return 0;
}
CF 632 E. Thief in a Shop
题意:
一个商店有\(n\)种物品,每种物品的价值为\(a_i\),并且每种物品有无限多个,现在你可以买\(k\)个,要求你求出买\(k\)个物品所有可能组合成的价值。
题解:
方法1(生成函数+NTT/FTT):
由于最后需要求的是价值的和,所以考虑吧价值作为\(x\)的幂次来卷积,系数为1代表选这个物品。那么生成函数为\(F(x)=\sum_{i=1}^nx^{a_i} +1\),求\(k\)个物品的价值就是把这个生成函数\(F(x)\)自己和自己卷积\(k\)次。
具体做法是把\(F(x)\)先用一次\(NTT/FFT\)转化为点值表示后,对每个系数求\(k\)次幂的值,然后再\(NTT/FFT\)转化为系数表示(我也不确定对不对)
坑点:如果用,这里一次\(NTT\)会出现冲突,所以要用2个模数来进行两次\(NTT\)避免冲突
\(NTT\)做法:
#include
using namespace std;
const int N=2e6+5;
const int G=3;
#define ll long long
template void rd(T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
vectorA(N),B(N);
int vis[N];
int RR[N];
int limit,L;
ll qmi(ll x,ll y,ll mod)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
void init(int n)
{
limit = 1, L = 0;
while(limit <=1000000) limit <<= 1, L ++ ;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
}
void NTT(vector&a,int type,ll mod)
{
for(int i=0;i
\(FTT\)做法:
利用多项式快速幂,快速幂的时候维护多项式的度数。\(O(nlog^2n)\)
#include
#define ll long long
using namespace std;
const int N = 2e6+10;
template void rd(T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
s*=f;
}
namespace FFT
{
const double PI = acos(-1);
int rev[N], bit, limit;
struct Complex
{
double x, y;
Complex operator+ (const Complex& t) const
{
return {x + t.x, y + t.y};
}
Complex operator- (const Complex& t) const
{
return {x - t.x, y - t.y};
}
Complex operator* (const Complex& t) const
{
return {x * t.x - y * t.y, x * t.y + y * t.x};
}
}p1[N],p2[N];
void fft(Complex a[], int type)
{
for (int i = 0; i < limit; i ++ )
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int mid = 1; mid < limit; mid <<= 1)
{
auto wn = Complex({cos(PI / mid), type * sin(PI / mid)});
for (int i = 0; i < limit; i += mid * 2)
{
auto w = Complex({1, 0});
for (int j = 0; j < mid; j ++, w = w * wn)
{
auto x = a[i + j], y = w * a[i + j + mid];
a[i + j] = x + y, a[i + j + mid] = x - y;
}
}
}
}
void poly_mul(Complex *A,Complex *B,int n,int m,Complex *res)
{
limit=1,bit=0;
while(limit<=n+m) limit<<=1,bit++;
for(int i=0;i<=limit;i++) p1[i]=p2[i]={0,0},rev[i]=0;
for (int i = 0; i < limit; i ++ )
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
for(int i=0;i>=1,n+=n-1;
}
}
}
using namespace FFT;
Complex a[N];
Complex res[N];
int mx=0;
int main()
{
int n,k;
rd(n),rd(k);
for (int i = 0; i
方法2(完全背包)
将所有价值排序,将所有价值除最小的外减去最小的价值,定义\(dp[w]\)为价值为\(w\)时最少需要多少物品,统计时不包括最小的数,则最后统计答案时,如果\(dp[w]\le k\),说明价值为\(w+min\_val*(k-dp[w])+min\_val*dp[w]=w+min\_val*k\)可以被凑出来(后面又加一个\(min\_val*dp[w]\)是因为转移时所有的价值都减去了一个\(min\_val\)。(补差价的思想)
#include
using namespace std;
const int N=1e6+10;
template void rd(T &x)
{
x=0;int f=1;char s=getchar();
while(s<'0'||s>'9') {if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
x*=f;
}
vectora,ans;
int dp[N];
int n,k;
int main()
{
rd(n),rd(k);
for(int i=0;i
CF 954 I. Yet Another String Matching Problem
题意
给定两个字符串\(S\)和\(T\),对于\(S\)的一个长度为\(|T|\)的子串(即以下标\(1,2,...,|S|-|T|+1\)开头长度为\(|T|\)的子串),可以进行多次操作将该子串\(S^{'}\)变为\(T\),每次操作是将选定\(S^{'}\)里面的一种字母\(a\),将该字母\(a\)全部替换成另一个字母\(a^{'}\),需要的最少操作成为这个子串\(S^{'}\)和\(T\)的距离,现在要你求所有子串对\(T\)的距离。
\(1\le |T| \le |S| \le 125000\)
\(S\)和\(T\)中只有字母\(a\)到\(f\)。
、
题解:
对于每一个子串,如果\(S_{i+j}^{'}\)和\(T_j\)不同并且不在一个集合里面,那么就将这两个字母所在的集合用并查集合并,并且\(ans++\),暴力的时间复杂度时\(O(|S||T|)\),显然会超时。
考虑优化:主要快速找到\(S\)中的某个字母\(a\)和\(T\)中的某个字母\(b\)是否需要在一个集合里。
abcdefa
ddcb
1. 1234
abcd
ddcb
2. 2345
bcde
ddcb
3. 3456
cdef
ddcb
4. 4567
defa
ddcb
再以\(S\)中的字母\(b\),\(T\)中的字母\(d\)为例:
1.(2,2) 2.(2,1)
发现2-2=0是对第1个字串的贡献,2-1=1是对第2个字串的贡献
也就是下标\(b\)出现的下标\(x\)减去\(d\)出现的下标\(y\)就是对第\(x-y+1\)个字串的贡献,那么怎么快速求呢?
尝试构造一个这样的生成函数:
对于\(S\)关于\(b\)的生成函数,如果\(b\)在下标\(k_1\)出现了,那么\(x^{k_1}\)项的系数为\(1\),否则为\(0\)。
对于\(T\)对于\(d\)的生成函数,如果\(d\)在下标\(k_2\)出现了,那么\(x^{|T|+1-k_2}\)的系数为\(1\),否则为\(0\). (对于减法通常反转或加一个偏移量保证为正)
这样在卷积的时候如果某一项\(x^k\)前面的系数不为\(0\),那么说明\(b\)和\(d\)在第\(k-m\)个集合中需要在一个集合里,这里用并查集合并一下就可以。
那么对于\(S\)关于\(b\)的生成函数为\(x^2\),\(T\)关于\(d\)生成函数为\(x^4+x^3\)
\(x^2(x^3+x^4)=x^5+x^6\),那么\(b\)和\(d\)在子串\(5-4=1\)和子串\(6-4=2\)需要在一个集合里面。
具体流程
枚举S中的字母i
枚举T中的字母j
{
构造S对于i的生成函数a
构造S对于j的生成函数b
a*b(卷积)
统计i和j需要在第几个子串中在一个集合中并合并。
}
对于第i个子串
枚举每一个字母j
如果子串i中的字母j所在的集合和j所在的集合不相等,说明子串中的字母j需要操作,ans++.
代码(好像用NTT一直会超时)
//#pragma GCC optimize(2)
#include
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 4e5+10;
const int p = 998244353, G = 3, ig = 332738118, img = 86583718;//1004535809
const int P = 998244353;
const int M=3e5+10;
const double PI=acos(-1);
template void read(T &x)
{
x = 0;
register int f = 1;
register char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
struct Complex
{
double x, y;
Complex operator+ (const Complex& t) const
{
return {x + t.x, y + t.y};
}
Complex operator- (const Complex& t) const
{
return {x - t.x, y - t.y};
}
Complex operator* (const Complex& t) const
{
return {x * t.x - y * t.y, x * t.y + y * t.x};
}
}a[N], b[N];
int rev[N], bit, tot;
void fft(Complex a[], int inv)
{
for (int i = 0; i < tot; i ++ )
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int mid = 1; mid < tot; mid <<= 1)
{
auto wn = Complex({cos(PI / mid), inv * sin(PI / mid)});
for (int i = 0; i < tot; i += mid * 2)
{
auto w = Complex({1, 0});
for (int j = 0; j < mid; j ++, w = w * wn)
{
auto x = a[i + j], y = w * a[i + j + mid];
a[i + j] = x + y, a[i + j + mid] = x - y;
}
}
}
}
int f[M][9];
char s[M],t[M];
int find(int k,int x)
{
return f[k][x]==x?x:f[k][x]=find(k,f[k][x]);
}
int main()
{
scanf("%s %s",s+1,t+1);
int n=strlen(s+1),m=strlen(t+1);
while((1<>1]>>1)|((i&1)<<(bit-1));
for(int i=1;i<=n-m+1;i++)
for(int j=0;j<6;j++)
f[i][j]=j;
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
{
if(i==j) continue;
for(int k=0;k0)
{
int x=find(k,i),y=find(k,j);
if(x!=y) f[k][x]=y;
}
}
}
for(int i=1;i<=n-m+1;i++)
{
int ans=0;
for(int j=0;j<6;j++) if(f[i][j]!=j) ans++;
printf("%d ",ans);
}
return 0;
}
CF 1096G - Lucky Tickets
题意
给定两个整数\(n\)和\(k\),然后给出\(k\)个在\(0-9\)范围内的整数\(d_i\),求用这个\(k\)个数可以组合成多少个长度为\(n\)的序列,满足前\(\sum_{i=1}^{\frac{n}{2}}a_i=\sum_{i=\frac{n}{2}+1}^na_i\) (注意:\(n\)一定为偶数,且\(k\)个数不一定全部需要使用)
\(1\le n \le 2\times 10^5\)
题解
先考虑暴力做法:枚举可以前\(n/2\)个数可以组合成的和的方案数,用\(f[i][m]\)表示枚举到第\(i\)位,和为\(m\)的方案数,那么转移就是
f[1][0]=1;
for(int i=1;<=n/2;i++)
for(int k=1;k<=K;k++)
for(m=d[k];m<=M;m++)
f[i][m]=f[i-1][m-d[k]]!=0?f[i-1][m-d[k]]+1:0;
for(int i=0;i<=M;i++)
ans=ans+f[n/2][i]*f[n/2][i];
这样的时间复杂度是\(O(NKM)\)的,显然不行。
考虑如何快速求\(f[n/2][i]\)
考虑生成函数\((x^{d_1}+x^{d_2}+...+x^{d_k})\),那么把这个生成函数卷\(n/2\)次后的多项式对应的\(x_i\)的系数就是\(f[n/2][i]\);
注意,这里用\(CDQ\)分治来求会\(TLE\),用\(NTT\)进行快速幂可以过。
//#pragma GCC optimize(2)
#include
#define ll long long
#define pb push_back
using namespace std;
const int N = 1100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809
const int M=18e5;
const int mod=998244353;
template void rd(T &x)
{
x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
vectorA(N);
namespace Poly
{
#define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
#define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了
typedef vector poly;
const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
//一般模数的原根为 2 3 5 7 10 6
const int inv_G = qpow(G, mod - 2);
int RR[N], deer[2][21][N], inv[N];
void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
for(int p = 1; p <= t; ++ p) {
int buf1 = qpow(G, (mod - 1) / (1 << p));
int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
deer[0][p][0] = deer[1][p][0] = 1;
for(int i = 1; i < (1 << p); ++ i) {
deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
}
}
inv[1] = 1;
for(int i = 2; i <= (1 << t); ++ i)
inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
}
int NTT_init(int n) {//快速数论变换预处理
int limit = 1, L = 0;
while(limit < n) limit <<= 1, L ++ ;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
return limit;
}
void NTT(poly &A, int type, int limit) {//快速数论变换
A.resize(limit);
for(int i = 0; i < limit; ++ i)
if(i < RR[i])
swap(A[i], A[RR[i]]);
for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
int len = mid >> 1;
for(int pos = 0; pos < limit; pos += mid) {
int *wn = deer[type][j];
for(int i = pos; i < pos + len; ++ i, ++ wn) {
int tmp = 1ll * (*wn) * A[i + len] % mod;
A[i + len] = ck(A[i] - tmp + mod);
A[i] = ck(A[i] + tmp);
}
}
}
if(type == 0) {
for(int i = 0; i < limit; ++ i)
A[i] = 1ll * A[i] * inv[limit] % mod;
}
}
inline poly poly_mul(poly A, poly B) {//多项式乘法
int deg = A.size() + B.size() - 1;
int limit = NTT_init(deg);
poly C(limit);
NTT(A, 1, limit);
NTT(B, 1, limit);
for(int i = 0; i < limit; ++ i)
C[i] = 1ll * A[i] * B[i] % mod;
NTT(C, 0, limit);
C.resize(deg);
return C;
}
//多个多项式相乘CDQ或者利用优先队列启发式合并
inline poly CDQ(int l,int r)
{
if(l==r)
{
return A;
}
int mid=l+r>>1;
poly L=CDQ(l,mid);
poly R=CDQ(mid+1,r);
return poly_mul(L,R);
}
}
ll qmi(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
using namespace Poly;
int n, k;
int main()
{
//freopen("in.in","r",stdin);
init(20);//2^21 = 2,097,152,根据题目数据多项式项数的大小自由调整,注意大小需要跟deer数组同步(21+1=22)
int n,k;
rd(n),rd(k);
for(int i=1;i<=k;i++)
{
int x;
rd(x);
A[x]=1;
}
int limit=NTT_init(1<<20);
NTT(A,1,limit);
//for(int i=0;i<=9;i++) cout<
E. Nikita and Order Statistics
题意
给定一个长度为\(n\)的序列和一个数\(x\),对于每个\(k=0,1,2,..,n\),求多有少区间满足这个区间中有\(k\)个数小于\(x\)
\(1\le n \le 2\times 10^5\)
题解:
暴力思路很简单,前缀和统计有多少个数小于\(x\),那么答案暴力写法就是
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
ans[pre[i]-pre[j-1]]++;
但这样时间复杂度是\(O(n^2)\)的,不能\(AC\),考虑优化
上面暴力的时间瓶颈在于\(pre[i]-pre[j-1]\)操作,对于求任意两个数的差,可以将\(pre[i]\)和\(pre[j-1]\)作变为\(x^{n+pre[i]}\)和\(x^{n-pre[j-1]}\),这样统计每个\(x^k\)出现多少次作为系数,构造一个生成函数,做卷积运算用\(FFT\)优化即可,答案从\(2n\)到\(3n\)统计即可。对于\(0\)这个答案,由于每个数自己和自己都会减一次,所以要减去\(n-1\),又由于个数相同的区间会互相减,所以最后答案还要除以2.
坑点(数组一定要到1<<20,不然会RT)
//#pragma GCC optimize(2)
#include
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const int p = 998244353, G = 3, ig = 332738118, img = 86583718;//1004535809
const int P = 998244353;
const double PI=acos(-1);
template void rd(T &x)
{
x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
struct Complex
{
double x, y;
Complex operator+ (const Complex& t) const
{
return {x + t.x, y + t.y};
}
Complex operator- (const Complex& t) const
{
return {x - t.x, y - t.y};
}
Complex operator* (const Complex& t) const
{
return {x * t.x - y * t.y, x * t.y + y * t.x};
}
};
int rev[1<<20], bit, tot=1;
void FFT(Complex a[], int inv)
{
for (int i = 0; i < tot; i ++ )
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int mid = 1; mid < tot; mid <<= 1)
{
auto wn = Complex({cos(PI / mid), inv * sin(PI / mid)});
for (int i = 0; i < tot; i += mid * 2)
{
auto w = Complex({1, 0});
for (int j = 0; j < mid; j ++, w = w * wn)
{
auto x = a[i + j], y = w * a[i + j + mid];
a[i + j] = x + y, a[i + j + mid] = x - y;
}
}
}
}
Complex A[1<<20],B[1<<20];
int pre[N];
int a[N];
int main()
{
ll n,x;
rd(n),rd(x);
for(int i=1;i<=n;i++)
{
ll num;
rd(num);
pre[i]=pre[i-1];
if(num>1]>>1)|((i&1)<<(bit-1));
FFT(A,1),FFT(B,1);
for(int i=0;i>1);
for(int i=2*n+1;i<=3*n;i++)
printf("%lld ",(ll)(A[i].x/tot+0.5));
return 0;
}