zoukankan      html  css  js  c++  java
  • nflsoj360【六校联合训练 省选 #2】染色问题(color)

    nflsoj360【六校联合训练 省选 #2】染色问题(color)

    题目大意

    题面下载

    nflsoj题目链接

    一个长度为(n)的序列,进行(m)次染色操作,每次操作可以将一段区间染成一种新颜色(区间长度大于等于(1)),要求最后得到的序列每个位置上必须有颜色。 一个格子可以被多次涂色,颜色为最后涂上的颜色。

    求最后不同的序列数(mod 998244353)。两个序列({a_i},{b_i})被认为不同,当且仅当(exist kin[1,n]:a_k eq b_k)

    数据范围:(1leq n,mleq 10^6)

    备注:题面里有一档部分分(n,mleq 100),实际数据中是(n,mleq 300)

    本题题解

    我们先不考虑“先染的颜色会被后染的颜色覆盖”这件事。如果某种颜色在最终的序列中出现了(x)次,我们就直接认为在染这种颜色的时候,我们只染了(x)个格子。也就是说,现在每次染的颜色,在序列里不一定是连续的一段。


    备注,“思路1”与正解关系不大,想看正解可以直接跳到思路2。

    思路1:有一种思路是,把条件转化为,有一个长度为(n)的数列({a_i}),使得对所有(iin [1,n]),不存在某种个大于(a_i)的数,在(i)左右两边都出现过。

    然后设(dp[i][j])表示一段长度为(i)的序列,用到了(j)个颜色(注意,要求这(j)种颜色都真的在最终的(i)个位置上出现过。我们在初步转化后,已经不谈“覆盖”这件事了)。

    在这种初步转化的思路下,按照每种颜色第一次和最后一次的出现位置,两种颜色要么是“并列”的关系,要么是“包含”的关系。所以转移时,枚举最左边的一整段非并列关系的区间长度,再枚举里面的颜色数量,转移要乘上组合数。

    时间复杂度(O(n^4))。但是这种初步转化的思路,似乎没什么优化前途。


    思路2:另一种思路是,不要直接转化成对序列计数。我们还是依次进行这(m)个操作。那么现在每次操作,相当于在原来的操作序列上,插入了一段新颜色。也就是说,我们不考虑元素的真实位置了,只考虑目前已有的元素的相对位置关系,然后整个序列——通过在中间插入一段新颜色——在不断变长。

    按此思路,也可以设计出一个DP。设(dp[i][j])表示进行了前(i)次操作(也就是插入了前(i)种新颜色)后,当前序列长度为(j)时的方案数。转移时,枚举插入新颜色前,序列的长度:

    [dp[i][j]=[i<m] imes dp[i-1][j]+sum_{k=0}^{i-1}dp[i-1][k] imes(k+1) ]

    其中,最前面的([i<m]),表示除了最后一次操作外,其他操作插入的段长度都可以为(0);最后一次操作插入的段长度至少是(1),因为最后一种颜色不会被其他颜色覆盖,所以它一定要在最终的序列里出现。

    时间复杂度(O(n^3)),可以用前缀和优化到(O(n^2))


    对比思路1和思路2

    思路1上来就把问题转化成对序列计数。但是这样,相当于所有位置必须在一开始就固定。状态缺乏灵活性,复杂度较差。

    而思路2则把问题看成是在动态地插入元素。这样相当于所有位置都是不固定的,随时可能在中间插一段进来。这正好与题目的性质相契合(两种颜色,要么并列,要么包含。所谓包含,就是在中间插入。所谓并列,就是在两边插入),所以这种状态得到了较好的复杂度。

    于是接下来,我们将抛弃思路1。顺着思路2,得到正解。


    观察思路2里DP的转移,主要就是( imes(k+1))

    发现,如果最终实际使用(j)种颜色,则方案数就是(prod_{i=1}^{n-1}(x+i+1))(x^{n-j})项前的系数。这可以结合DP转移来理解,也可以根据具体含义理解:如果实际使用了(j)种颜色,每次染色前已有的序列长度为(a_1,a_2,dots ,a_j),那么一定有:(0=a_1<a_2<dots<a_j<n),方案数就是(prod_{i=2}^{n}(a_i+1))。总方案数,是所有这样的(a)序列的贡献之和,就相当于在([2,n])里选出(j-1)个数,它们的乘积之和。这也就是(prod_{i=1}^{n-1}(x+i+1))(x^{n-j})项前的系数。

    ( ext{res}_j=[x^{n-j}](prod_{i=1}^{n-1}(x+i+1))),则最终答案就是(sum_{j=1}^{min(n,m)} ext{res}_j imes{m-1choose j-1})。之所以要乘({m-1choose j-1}),是因为要确定选出的是哪(j)种颜色,并且颜色(m)是必选的,所以只需要确定其他(j-1)种颜色即可。

    于是我们要求一堆小多项式的乘积。它们的乘积一定是一个(n-1)次的多项式。可以用分治FFT求解。(这是比较套路的用法,如果不会建议学习一下分治FFT)。

    时间复杂度(O(nlog^2n))


    继续优化。考虑用倍增代替分治。

    假设已经求出了(F_{t}(x)=prod_{i=1}^{t}(x+i+1))这个(t)次多项式。如果我们用倍增解决本题,相当于要处理两个问题:

    1. 如何从(F_t(x))推出(F_{t+1}(x))
    2. 如何从(F_{t}(x))推出(F_{2t}(x))

    第1个问题比较简单:(F_{t+1}(x)=F_{t}(x) imes(x+t+2))。可以线性推出来。

    第2个问题,可以写成:(F_{2t}(x)=F_{t}(x) imes F_{t}(x+t))

    (F_{t}(x)=sum_{i=0}^{t}f_ix^i),那么(F_{t}(x+t))就等于(sum_{i=0}^{t}f_i imes(x+t)^i)

    也就是说,(f_{0dots t})这个数组是我们已经知道的。我们要通过它,求出(F_{2t}(x)=F_{t}(x) imes F_{t}(x+t))

    考虑先用一个数组(a_{0dots t})表示出(F_{t}(x+t)),再对(a,f)做多项式乘法。问题转化为求这个数组(a)。观察(F_{t}(x+t))的式子,考虑用二项式定理展开:

    [egin{align} F_{t}(x+t)&=sum_{i=0}^{t}f_i imes(x+t)^i\ &=sum_{i=0}^{t}f_isum_{j=0}^{i}x^jt^{i-j}{ichoose j}\ &=sum_{j=0}^{t}x^jsum_{i=j}^{t}f_it^{i-j}frac{i!}{j!(i-j)!} end{align} ]

    (b_i=f_i imes i!)(c_i=t^{i} imesfrac{1}{i!})。则:

    [a_i=frac{1}{i!}sum_{j=i}^{t}b_jc_{j-i} ]

    这其实也是一个卷积。简单讲就是同时反转(a,b),就能变成通常意义上的卷积了。

    用一次卷积求出(a),再对(a,f)做一次卷积求出结果。时间复杂度(O(nlog n))

    那么倍增的总时间复杂度就是(T(n)=T(frac{n}{2})+O(nlog n)=O(nlog n))。可以通过(nleq 10^6)的数据。

    参考代码:

    //problem:nflsoj360
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=1e6;
    const int MOD=998244353;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int& x,int y){x=mod1(x+y);}
    inline void sub(int& x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    int fac[MAXN+5],ifac[MAXN+5];
    inline int comb(int n,int k){
    	if(n<k)return 0;
    	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
    }
    void facinit(int lim=MAXN){
    	fac[0]=1;
    	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    	ifac[lim]=pow_mod(fac[lim],MOD-2);
    	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
    }
    
    namespace PolyNTT{
    int rev[MAXN*4+5];
    int f[MAXN*4+5],g[MAXN*4+5];
    void NTT(int *a,int n,int flag){
    	for(int i=0;i<n;++i)if(i<rev[i])swap(a[i],a[rev[i]]);
    	for(int i=1;i<n;i<<=1){
    		int T=pow_mod(3,(MOD-1)/(i<<1));
    		if(flag==-1) T=pow_mod(T,MOD-2);
    		for(int j=0;j<n;j+=(i<<1)){
    			for(int k=0,t=1;k<i;++k,t=(ll)t*T%MOD){
    				int Nx=a[j+k],Ny=(ll)a[i+j+k]*t%MOD;
    				a[j+k]=mod1(Nx+Ny);
    				a[i+j+k]=mod2(Nx-Ny);
    			}
    		}
    	}
    	if(flag==-1){
    		int invn=pow_mod(n,MOD-2);
    		for(int i=0;i<n;++i)a[i]=(ll)a[i]*invn%MOD;
    	}
    }
    void mul(int n,int m){
    	int lim=1,ct=0;
    	while(lim<=n+m)lim<<=1,ct++;
    	for(int i=n;i<=lim;++i)f[i]=0;
    	for(int i=m;i<=lim;++i)g[i]=0;//clear
    	for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(ct-1));
    	NTT(f,lim,1);
    	NTT(g,lim,1);
    	for(int i=0;i<lim;++i)f[i]=(ll)f[i]*g[i]%MOD;
    	NTT(f,lim,-1);
    }
    }//namespace PolyNTT
    
    typedef vector<int> Poly;
    
    Poly operator*(const Poly& a,const Poly& b){
    	if(!SZ(a) && !SZ(b))return Poly();
    	Poly res;
    	res.resize(SZ(a)+SZ(b)-1);
    	if(SZ(a)<=50 && SZ(b)<=50){
    		for(int i=0;i<SZ(a);++i)for(int j=0;j<SZ(b);++j)add(res[i+j],(ll)a[i]*b[j]%MOD);
    		return res;
    	}
    	for(int i=0;i<SZ(a);++i)PolyNTT::f[i]=a[i];
    	for(int i=0;i<SZ(b);++i)PolyNTT::g[i]=b[i];
    	PolyNTT::mul(SZ(a),SZ(b));
    	for(int i=0;i<SZ(res);++i)res[i]=PolyNTT::f[i];
    	return res;
    }
    Poly& operator*=(Poly& lhs,const Poly& rhs){
    	lhs=lhs*rhs;return lhs;
    }
    Poly operator+(const Poly& a,const Poly& b){
    	Poly res;
    	res.resize(max(SZ(a),SZ(b)));
    	for(int i=0;i<SZ(res);++i){
    		res[i]=mod1((i>=SZ(a)?0:a[i])+(i>=SZ(b)?0:b[i]));
    	}
    	return res;
    }
    Poly& operator+=(Poly& lhs,const Poly& rhs){
    	lhs=lhs+rhs;return lhs;
    }
    
    int n,m;
    
    Poly solve(int n){
    	// prod_{i=1->n} (x+i+1)
    	if(n==1){
    		Poly res(2);
    		res[0]=2;
    		res[1]=1;
    		return res;
    	}
    	if(n&1){
    		// 奇数
    		Poly res=solve(n-1);
    		assert(SZ(res)==n);
    		res.push_back(0);
    		for(int i=n;i>=1;--i){
    			res[i]=((ll)res[i]*(n+1) + res[i-1])%MOD;
    		}
    		res[0]=(ll)res[0]*(n+1)%MOD;
    		return res;
    	}
    	// 偶数
    	int len=n/2;
    	Poly f=solve(len);
    	assert(SZ(f)==len+1);
    	Poly a,b,c;
    	b.resize(len+1);
    	c.resize(len+1);
    	int pow_of_len=1;
    	for(int i=0;i<=len;++i){
    		b[i]=(ll)f[i]*fac[i]%MOD;
    		c[i]=(ll)pow_of_len*ifac[i]%MOD;
    		pow_of_len=(ll)pow_of_len*len%MOD;
    	}
    	reverse(b.begin(),b.end());
    	a=b*c;
    	while(SZ(a)>len+1) a.pop_back();
    	reverse(a.begin(),a.end());
    	for(int i=0;i<=len;++i){
    		a[i]=(ll)ifac[i]*a[i]%MOD;
    	}
    	return f*a;
    }
    int main() {
    	facinit();
    	cin>>n>>m;
    	Poly res=solve(n-1);
    	int ans=0;
    	for(int i=1;i<=n && i<=m;++i){
    		// 实际使用了i种颜色
    		ans=((ll)ans + (ll)res[n-i]*comb(m-1,i-1))%MOD;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    如何用机器学习强化市场营销活动。
    大数据统计脚本, 分城市订单统计
    宇宙常量与增长黑客。
    病毒传播效果的衡量公式
    浅谈对增长黑客的理解
    大数据分析, 数据挖掘, 机器学习,找到产品改进的爆点。
    R语言的日期运算
    安装语言包-英文(美国)
    selenium page objects
    logging模块
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13603973.html
Copyright © 2011-2022 走看看