多项式泛做1
多项式泛做1
2021 icpc Shanghai B
题意:给定一个长度为的排列,要求计算出有多少种长度也为的排列满足,。最后答案对取模。
Sol:对于序列,先找出有多少个环并且计算环的大小,比如现在序列,根据下标的对应关系构造环,,和连边有向边,然后找,发现,所以在一个环中,同理也在一个环中。假设我们现在有个环,每个环的大小是,再来看问题要求,可以发现最后构造的相邻的两个数和不能有有向边直接相连,这里的有向边的方向可以看做序列中的顺序,可以手动模拟一下。进一步把选点问题变成选边问题,考虑二项式反演,恰好对转换成至少对,最后问题就可以转化成不能选在环中出现的边的方案,那么,其中为至少选条在环中的边,构造生成函数 ,那么,最后乘个就是钦定对相邻的在环中的序列的方案,现在来证明:
假设选出了条边,这条边包含了个不同的点,因为可能选出的边连在一起,如果没有连在一起的,那么点数就是了,把不连在一起的边称为自由边,那么自由边有条,然后自由点有个,这些自由点和自由边总共有,然后把这个自由的全排列即可,所以就是
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#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 <typename T>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;
}
vector<ll>C[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<ll> 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<cir[i];j++)
C[i].push_back(cal(cir[i],j));
ll ans=0;
Poly::poly res=Poly::CDQ(1,cnt);
//degbug一个多小时,这里要把res重新赋值给xs,因为res的大小可能小于n
for(int i=0;i<res.size();i++) xs[i]=res[i];
for(int i=0;i<n;i++)
{
if(i&1) ans=(ans-f[n-i]*xs[i]%mod+mod)%mod;
else ans=(ans+f[n-i]*xs[i]%mod)%mod;
}
printf("%lld\n",(ans+mod)%mod);
return 0;
}
CF 1218 E. Product Tuples(分治NTT,生成函数)
题意:
给定一个长度为的序列和一个整数,定义元组(即元素个数为的集合)的贡献为这个元组里面所有数的乘积,求的所有可能形成的元组((即元素个数为的子集)的贡献和。
题解:
有点类似将一个数分解素数分解后求它的约数个数和的思想。用到生成函数,答案其实就是求,最后的答案就是的系数。如果暴力用求的话要进行次,时间复杂度约为,显然不行,所以用分治,时间复杂度将为。
扩展:
其实这里就是多个多项式相乘,有两种思路,一种是上述的,另一种是启发式合并,维护一个小根堆,每次把度数小的两个多项式做乘法(好像是,还不确定,有待考察)
Code
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
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 <typename T>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<ll> 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
题意:
一个商店有种物品,每种物品的价值为,并且每种物品有无限多个,现在你可以买个,要求你求出买个物品所有可能组合成的价值。
题解:
方法1(生成函数+NTT/FTT):
由于最后需要求的是价值的和,所以考虑吧价值作为的幂次来卷积,系数为1代表选这个物品。那么生成函数为,求个物品的价值就是把这个生成函数自己和自己卷积次。
具体做法是把先用一次转化为点值表示后,对每个系数求次幂的值,然后再转化为系数表示(我也不确定对不对)
坑点:如果用,这里一次会出现冲突,所以要用2个模数来进行两次避免冲突
做法:
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5;
const int G=3;
#define ll long long
template <typename T> 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;
}
vector<ll>A(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<ll>&a,int type,ll mod)
{
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=qmi(G,(mod-1)/(mid<<1),mod);
if(type==-1) wn=qmi(wn,mod-2,mod);
for(int i=0,len=mid<<1;i<limit;i+=len)
{
ll w=1;
for(int j=0;j<mid;j++,w=w*wn%mod)
{
ll x=a[i+j],y=w*a[i+j+mid]%mod;
a[i+j]=(x+y)%mod,a[i+j+mid]=(x-y+mod)%mod;
}
}
}
if(type==-1)
{
ll inv=qmi(limit,mod-2,mod);
for(int i=0;i<limit;i++)
a[i]=a[i]*inv%mod;
}
}
int main()
{
int n,k;
ll mod;
rd(n),rd(k);
for(int i=1;i<=n;i++)
{
int x;rd(x);
A[x]=B[x]=1;
}
init(N);
mod=998244353;
NTT(A,1,mod);
for(int i=0;i<limit;i++) A[i]=qmi(A[i],k,mod);
NTT(A,-1,mod);
for(int i=0;i<limit;i++) if(A[i]) vis[i]=1;
mod=1004535809;
NTT(B,1,mod);
for(int i=0;i<limit;i++) B[i]=qmi(B[i],k,mod);
NTT(B,-1,mod);
for(int i=0;i<limit;i++) if(B[i]) vis[i]=1;
for(int i=1;i<N;i++)
if(vis[i]) printf("%d ",i);
return 0;
}
做法:
利用多项式快速幂,快速幂的时候维护多项式的度数。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e6+10;
template <typename T>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<n;i++) p1[i]=A[i];
for(int i=0;i<m;i++) p2[i]=B[i];
fft(p1,1),fft(p2,1);
for(int i=0;i<limit;i++) p1[i]=p1[i]*p2[i];
fft(p1,-1);
for (int i = 0; i <limit; i ++ )
if((int)(p1[i].x / limit + 0.5))
res[i].x=1;
else res[i].x=0;
}
void poly_qmi(Complex *A,int n,int y,Complex *res)
{
int len=1;
res[0].x=1;
while(y)
{
if(y&1) poly_mul(res,A,len,n,res),len+=n-1;
poly_mul(A,A,n,n,A);
y>>=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 <n; i ++ )
{
int x;
rd(x);
mx=max(mx,x);
a[x]={1,0};
}
poly_qmi(a,mx+1,k,res);
for(int i=1;i<=1000000;i++)
if((int)res[i].x) printf("%d ",i);
return 0;
return 0;
}
方法2(完全背包)
将所有价值排序,将所有价值除最小的外减去最小的价值,定义为价值为时最少需要多少物品,统计时不包括最小的数,则最后统计答案时,如果,说明价值为可以被凑出来(后面又加一个是因为转移时所有的价值都减去了一个。(补差价的思想)
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
template <typename T> 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;
}
vector<int>a,ans;
int dp[N];
int n,k;
int main()
{
rd(n),rd(k);
for(int i=0;i<n;i++)
{
int x;
rd(x);
a.push_back(x);
}
sort(a.begin(),a.end());
a.erase(unique(a.begin(),a.end()),a.end());
int m=a.size();
memset(dp,0x3f,sizeof dp);
for(int i=1;i<m;i++) a[i]-=a[0];
dp[0]=0;
for(int i=1;i<m;i++)
for(int j=a[i];j<=a[m-1]*k;j++)
dp[j]=min(dp[j],dp[j-a[i]]+1);
for(int i=0;i<=a[m-1]*k;i++)
if(dp[i]<=k) printf("%d ",a[0]*(k-dp[i])+i);
return 0;
}
CF 954 I. Yet Another String Matching Problem
题意
给定两个字符串和,对于的一个长度为的子串(即以下标开头长度为的子串),可以进行多次操作将该子串变为,每次操作是将选定里面的一种字母,将该字母全部替换成另一个字母,需要的最少操作成为这个子串和的距离,现在要你求所有子串对的距离。
和中只有字母到。
、
题解:
对于每一个子串,如果和不同并且不在一个集合里面,那么就将这两个字母所在的集合用并查集合并,并且,暴力的时间复杂度时,显然会超时。
考虑优化:主要快速找到中的某个字母和中的某个字母是否需要在一个集合里。
abcdefa
ddcb
1. 1234
abcd
ddcb
2. 2345
bcde
ddcb
3. 3456
cdef
ddcb
4. 4567
defa
ddcb
再以中的字母,中的字母为例:
1.(2,2) 2.(2,1)
发现2-2=0是对第1个字串的贡献,2-1=1是对第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 <bits/stdc++.h>
#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 <typename T>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<<bit)<=n*2) bit++;
tot=1<<bit;
for(int i=0;i<tot;i++)
rev[i]=(rev[i>>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;k<tot;k++) a[k]=b[k]={0,0};
for(int k=1;k<=n;k++) a[k]={(s[k]-'a'==i),0};
for(int k=1;k<=m;k++) b[m+1-k]={(t[k]-'a'==j),0};
fft(a,1),fft(b,1);
for(int k=0;k<tot;k++) a[k]=a[k]*b[k];
fft(a,-1);
for(int k=1;k<=n-m+1;k++)
{
if((int)(a[k+m].x/tot+0.5)>0)
{
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
题意
给定两个整数和,然后给出个在范围内的整数,求用这个个数可以组合成多少个长度为的序列,满足前 (注意:一定为偶数,且个数不一定全部需要使用)
题解
先考虑暴力做法:枚举可以前个数可以组合成的和的方案数,用表示枚举到第位,和为的方案数,那么转移就是
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];
这样的时间复杂度是的,显然不行。
考虑如何快速求
考虑生成函数,那么把这个生成函数卷次后的多项式对应的的系数就是;
注意,这里用分治来求会,用进行快速幂可以过。
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#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 <typename T>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;
}
vector<ll>A(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<ll> 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<<A[i]<<" ";
for(int i=0;i<limit;i++) A[i]=qmi(A[i],n/2);
NTT(A,0,limit);
ll ans=0;
for(int i=0;i<limit;i++)
ans=(ans+A[i]*A[i]%mod)%mod;
cout<<ans<<endl;
return 0;
}
E. Nikita and Order Statistics
题意
给定一个长度为的序列和一个数,对于每个,求多有少区间满足这个区间中有个数小于
题解:
暴力思路很简单,前缀和统计有多少个数小于,那么答案暴力写法就是
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
ans[pre[i]-pre[j-1]]++;
但这样时间复杂度是的,不能,考虑优化
上面暴力的时间瓶颈在于操作,对于求任意两个数的差,可以将和作变为和,这样统计每个出现多少次作为系数,构造一个生成函数,做卷积运算用优化即可,答案从到统计即可。对于这个答案,由于每个数自己和自己都会减一次,所以要减去,又由于个数相同的区间会互相减,所以最后答案还要除以2.
坑点(数组一定要到1<<20,不然会RT)
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#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 <typename T>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<x) pre[i]++;
}
for(int i=0;i<=n;i++)
{
A[n-pre[i]].x++;
B[n+pre[i]].x++;
}
while(tot<3*n) tot<<=1,bit++;
rev[0]=0;
for(int i=1;i<tot;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
FFT(A,1),FFT(B,1);
for(int i=0;i<tot;i++) A[i]=A[i]*B[i];
FFT(A,-1);
printf("%lld ",((ll)(A[n<<1].x/tot+0.5)-1-n)>>1);
for(int i=2*n+1;i<=3*n;i++)
printf("%lld ",(ll)(A[i].x/tot+0.5));
return 0;
}
常用模板
NTT
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#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 <typename T>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;
}
vector<ll>A(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<ll> 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 poly{1,A[l]};
}
int mid=l+r>>1;
poly L=CDQ(l,mid);
poly R=CDQ(mid+1,r);
return poly_mul(L,R);
}
}
FTT
#include<bits/stdc++.h>
#define db double
using namespace std;
struct FFTClass {
static constexpr int MAXL = 22;
static constexpr int MAXN = 1 << MAXL;
static constexpr db PI = acos(-1);
struct Complex {
db R, I;
Complex() {
R = I = 0;
}
Complex(db _R, db _I): R(_R), I(_I) {}
Complex operator + (const Complex &r) {
return Complex(R + r.R, I + r.I);
}
Complex operator - (const Complex &r) {
return Complex(R - r.R, I - r.I);
}
Complex operator * (const Complex &r) {
return Complex(R * r.R - I * r.I, R * r.I + I * r.R);
}
Complex operator / (const db d) {
return Complex(R / d, I / d);
}
};
using vd = vector<db>;
int rev[MAXN];
Complex a[MAXN], b[MAXN], c[MAXN];
void transform(int n, Complex *t, int typ) {
for (int i = 0; i < n; i++)
if (i < rev[i])
swap(t[i], t[rev[i]]);
for (int step = 1; step < n; step <<= 1) {
Complex gn(cos(PI / step), sin(PI / step));
//int gn=fast_pow(root,(MOD-1)/(step<<1));
for (int i = 0; i < n; i += (step << 1)) {
Complex g(1, 0);
for (int j = 0; j < step; j++, g = g * gn) {
Complex x = t[i + j], y = g * t[i + j + step];
t[i + j] = x + y;
t[i + j + step] = x - y;
}
}
}
if (typ == 1)
return;
for (int i = 1; i < n / 2; i++)
swap(t[i], t[n - i]);
for (int i = 0; i < n; i++)
t[i] = t[i] / n;
}
void fft(int p, Complex *A, Complex *B, Complex *C) {
transform(p, A, 1);
transform(p, B, 1);
for (int i = 0; i < p; i++)
C[i] = A[i] * B[i];
transform(p, C, -1);
}
void mul(Complex *A, Complex *B, Complex *C, int n, int m) {
int p = 1, l = 0;
while (p <= n + m)
p <<= 1, l++;
for (int i = n + 1; i < p; i++)
A[i] = Complex(0, 0);
for (int i = m + 1; i < p; i++)
B[i] = Complex(0, 0);
for (int i = 0; i < p; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
fft(p, A, B, C);
}
vd conv(vd &v1, vd &v2) {
int n = v1.size() - 1, m = v2.size() - 1; // degree of poly v1 and v2
for (int i = 0; i <= n; i++)
a[i] = Complex(v1[i], 0);
for (int i = 0; i <= m; i++)
b[i] = Complex(v2[i], 0);
mul(a, b, c, n, m);
vd ret(n + m + 1);
for (int i = 0; i < n + m + 1; i++) {
ret[i] = c[i].R;
}
return ret;
}
} FFT;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin>>n>>m;
n++;
m++;
vector<db> a(n), b(m);
for (int i = 0; i < n; i++) cin>>a[i];
for (int i = 0; i < m; i++) cin>>b[i];
auto c = FFT.conv(a, b);
for (int i = 0; i < c.size(); i++) {
printf("%d%c", int(c[i] + 1e-5), " \n"[i == c.size() - 1]);
}
}
NTT大模数提高精度
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
#define ll __int128
const ll inf=0x7fffffff,P=1945555039024054273, G = 5, Gi = 1167333023414432564;
ll r[N];
vector<ll> len,val[N];
bool vis[N];
ll quickpow(ll base,ll ex,ll mo)
{
ll ans;
for(ans=1;ex!=0;ex>>=1,base=base*base%mo)
if(ex&1) ans=ans*base%mo;
return ans;
}
void NTT(vector<ll> &a,int len,int t){
for(int i=0;i<len;i++){
if(i<r[i]){
swap(a[i],a[r[i]]);
}
}
for(ll mid=1;mid<len;mid<<=1){
ll wn=quickpow(t==1?G:Gi,(P-1)/(mid<<1),P);
for(ll j=0;j<len;j+=(mid<<1)){
ll w=1;
for(int k=0;k<mid;k++,w=w*wn%P){
ll x=a[j+k],y=w*a[j+k+mid]%P;
a[j+k]=(x+y)%P;
a[j+k+mid]=(x-y+P)%P;
}
}
}
if(t==-1){
ll inv=quickpow(len,P-2,P);
for(int i=0;i<len;i++){
a[i]=a[i]*inv%P;
}
}
}
vector<ll> operator *(vector<ll> a,vector<ll> b){
if(a.empty()||b.empty()) return vector<ll>();
ll l=0,limit=1;
ll an=a.size(),bn=b.size();
while(limit<=an+bn) limit<<=1,l++;
for(int i=0;i<limit;i++){
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
a.resize(limit);
b.resize(limit);
NTT(a,limit,1);NTT(b,limit,1);
for(int i=0;i<limit;i++){
a[i]=a[i]*b[i]%P;
}
NTT(a,limit,-1);
return a;
}
int main()
{
vector<ll>a(n),b(m);
auto c=a*b;
//__int128不可以直接输出,转换成ll
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~