多项式泛做1

多项式泛做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;
}

你可能感兴趣的:(多项式泛做1)