zoukankan      html  css  js  c++  java
  • 题解 CF1326F2 Wise Men (Hard Version)

    前置知识:快速沃尔什变换(FWT)

    or卷积

    给出两个序列(a,b),求一个序列(c),使得(c_i=sum_{joperatorname{OR}k=i}a_jb_k)

    仿照FFT的思路,我们构造两个序列(FWT(a),FWT(b))(对应了FFT里的点值),使得(FWT(c)[i]=FWT(a)[i]cdot FWT(b)[i])。然后再对(FWT(c))做逆变换,得到(c)

    FWT算法的结论是:对于or卷积,(FWT(a)[i]=sum_{joperatorname{OR}i=i}a_j)。可以发现,(joperatorname{OR}i=i)就等价于“(j)(i)的一个子集”。

    值得一提的是,根据这个定义,FWT-or就相当于是做高维前缀和;FWT-or的逆变换(IFWT-or)就相当于是高维前缀和的逆变换(差分)

    在实现时,对于一个最高次项为(2^n)的多项式(a),把它分成(a_0,a_1)两部分,分别表示前面的(2^{n-1})项和后面的(2^{n-1})项,则:

    [FWT(a)=egin{cases} (FWT(a_0),FWT(a_0+a_1))&&n>0\ a&&n=0 end{cases} ]

    这个逗号是啥意思?因为(FWT(a))是一个长度为(2^n)的序列,因此逗号左边就是序列的前(2^{n-1})项,右边就是序列的后(2^{n-1})项。

    而逆变换就把这个过程反过来即可,即:

    [IFWT(a)=egin{cases} (IFWT(a_0),IFWT(a_1-a_0))&& n>0\ a&&n=0 end{cases} ]

    and卷积

    给出两个序列(a,b),求一个序列(c),使得(c_i=sum_{joperatorname{AND}k=i}a_jb_k)

    对于and卷积,(FWT(a)[i]=sum_{joperatorname{AND}i=i}a_j)。可以发现,(joperatorname{AND}i=i)就等价于“(i)(j)的一个子集”,和or卷积恰好相反。

    同样可以看出,根据这个定义,FWT-and就相当于是做高维后缀和;FWT-and的逆变换(IFWT-and)就相当于是高维后缀和的逆变换(差分)

    在实现时,

    [FWT(a)=egin{cases} (FWT(a_0+a_1),FWT(a_1))&&n>0\ a&&n=0 end{cases} ]

    同理可以做逆变换:

    [IFWT(a)=egin{cases} (IFWT(a_0-a_1),IFWT(a_1))&&n>0\ a&&n=0 end{cases} ]

    xor卷积

    与本题无关。只是顺带提一下做法:

    [FWT(a)=egin{cases} (FWT(a_0+a_1),FWT(a_0-a_1))&&n>0\ a&&n=0 end{cases} ]

    于是可知,逆变换为:

    [IFWT(a)=egin{cases} (IFWT(frac{a_0+a_1}{2}),IFWT(frac{a_0-a_1}{2}))&&n>0\ a&&n=0 end{cases} ]

    本题题解

    我们设(ans(s))表示串(s)的答案。直接求(ans(s))不好求,考虑集合中至少包含(s)的答案,即所有(sin S)(ans(S))之和,记为(ans'(s))。然后我们对(ans')数组做IFWT-and卷积,就可以求出所有(ans(s))

    把朋友之间的关系看做一张无向图。我们定义一条链的长度为它经过的节点数

    那么对于一个长度为(n-1)的01串(s),它代表的其实是图中的若干条链。具体来讲,如果在串(s)后面补上一个(0),那么:

    • 串中每段连续的(1)是一条链。如果有(x)(1),则链的长度为(x+1)
    • 每个(0)是单独的一个节点(也就是一条长度为(1)的链)。特别地:一段连续的(1)之后的第一个(0)除外,它这个位置上的节点已经被计入了上一条连续的(1)组成的链中。

    按照上述规则,不难发现,所有链的长度之和恰好为(n)。而对于一个01串(s)来说,(ans'(s))只取决于它划分出的链的长度的可重集。例如:(ans'(0111011)=ans'(1100111)),因为它们的这个可重集都是({1,3,4})

    又因为所有链的长度之和恰好为(n),故本质不同的可重集数量只有(P(n))种,其中(P(n))表示(n)的划分数。(P(18)=385)。于是我们只需要对这(P(n))个“链的长度的可重集”,分别求答案。

    (f_{i,mask})表示对于一个大小为(i)的节点集合(mask),图中有多少条链,恰好经过(mask)中的这些节点。

    如果我们求出了(f_{i,mask})数组,那么对于一个“链的长度的可重集”(T),它的答案就是(displaystylesum_{m_1,dots,m_{|T|}} prod_{i=1}^{|T|}f_{len(T_i),m_i})。其中(len(T_i))表示(T)中第(i)条链的长度。前面的(sum)枚举的是一个(m_i)数组,表示对每个(i)各取一个大小为(len(T_i))的点集(m_i),要求这些(m_i)的并为([1,n])且互相不交。容易发现只要并为([1,n])就必然互相不交,因为它们的(len(T_i))之和为(n)。所以我们可以做一个FMT-or卷积。把所有(f_{len(T_i)})(|T|)个序列卷起来。卷积结果的(2^n-1)项前的系数即为(T)这个可重集的答案。

    现在最后的问题是(f_{i,mask})数组怎么求。可以做简单的状压DP。设(dp[mask][j])表示经过了(mask)中的这些节点,最后一个经过的节点为(j)的链的数量。转移时枚举一个不在(mask)中切与(j)有连边的点作为下一个点即可。则(f_{i,mask}=sum_{j=1}^{n}dp[mask][j])

    DP求(f_{i,mask})的复杂度为(O(2^nn^2)),之后枚举每个可重集,求答案的复杂度为(O(P(n)2^nn)),其中(P(18)=385)

    参考代码:

    //problem:CF1326F2
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fst first
    #define scd second
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    namespace Fread{
    const int MAXN=1<<20;
    char buf[MAXN],*S,*T;
    inline char getchar(){
    	if(S==T){
    		T=(S=buf)+fread(buf,1,MAXN,stdin);
    		if(S==T)return EOF;
    	}
    	return *S++;
    }
    }//namespace Fread
    #ifdef ONLINE_JUDGE
    	#define getchar Fread::getchar
    #endif
    inline int read(){
    	int f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    inline ll readll(){
    	ll f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    inline int readbit(){
    	char ch=getchar();
    	while(ch<'0'||ch>'1')ch=getchar();
    	return ch-'0';
    }
    /*  ------  by:duyi  ------  */ // myt天下第一
    const int MAXN=18;
    int n,a[MAXN+5][MAXN+5];
    ll dp[1<<MAXN][MAXN+5],f[MAXN+5][1<<MAXN],h[400],ans[1<<MAXN];
    
    int cnt;
    map<vector<int>,int>mp;
    vector<int>vec[400],tmp;
    void dfs(int cur,int lst){
    	if(cur==n+1){
    		mp[tmp]=++cnt;
    		vec[cnt]=tmp;
    		return;
    	}
    	if(n-cur+1<lst)return;
    	for(int i=lst;cur+i-1<=n;++i){
    		tmp.pb(i);
    		dfs(cur+i,i);
    		tmp.pop_back();
    	}
    }
    
    int bitcnt(uint x){
    	int res=0;
    	for(int j=0;j<=31;++j)res+=((x>>j)&1u);
    	return res;
    }
    void fwt_or(ll *f,uint n,int flag){
    	// FWT_or(A)[i] = sum_{j|i=i} A[j]
    	//即:j是i的一个子集
    	for(uint i=1;i<n;i<<=1){
    		for(uint j=0;j<n;j+=(i<<1)){
    			for(uint k=0;k<i;++k){
    				f[i+j+k]+=f[j+k]*flag;
    			}
    		}
    	}
    }
    void fwt_and(ll *f,uint n,int flag){
    	// FWT_and(A)[i] = sum_{j&i=i} A[j]
    	//即:i是j的一个子集
    	for(uint i=1;i<n;i<<=1){
    		for(uint j=0;j<n;j+=(i<<1)){
    			for(uint k=0;k<i;++k){
    				f[j+k]+=f[i+j+k]*flag;
    			}
    		}
    	}
    }
    
    int main() {
    	n=read();
    	dfs(1,1);//搜出所有划分数
    	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)a[i][j]=readbit();
    	
    	//dp[mask][j] 表示经过了mask中这些点,以j结尾的链有多少.
    	//用来求出 f[i][mask] 表示经过了大小为i的集合mask的链的数量
    	for(int i=1;i<=n;++i)dp[1u<<(i-1)][i]=1;
    	for(uint i=1;i<(1u<<n);++i){
    		for(int j=1;j<=n;++j)if((i>>(j-1))&1u){
    			for(int k=1;k<=n;++k)if(a[j][k]&&!((i>>(k-1))&1u)){
    				dp[i|(1u<<(k-1))][k]+=dp[i][j];
    			}
    		}
    		int t=bitcnt(i);
    		for(int j=1;j<=n;++j)f[t][i]+=dp[i][j];
    	}
    	
    	for(int i=1;i<=n;++i)fwt_or(f[i],1u<<n,1);
    	static ll IE[1<<MAXN],tmp[1<<MAXN];
    	IE[0]=1;
    	fwt_or(IE,1u<<n,1);
    	for(int i=1;i<=cnt;++i){
    		for(uint j=0;j<(1u<<n);++j)tmp[j]=IE[j];
    		for(uint j=0;j<vec[i].size();++j){
    			for(uint k=0;k<(1u<<n);++k)tmp[k]=(ll)tmp[k]*f[vec[i][j]][k];
    		}
    		fwt_or(tmp,1u<<n,-1);
    		h[i]=tmp[(1u<<n)-1];
    	}
    	for(uint i=0;i<(1u<<(n-1));++i){
    		vector<int>tmp;
    		for(int j=0;j<=n-1;){
    			int jj=j+1;
    			while(jj-1<=n-2 && ((i>>(jj-1))&1u))++jj;
    			tmp.pb(jj-j);
    			j=jj;
    		}
    		sort(tmp.begin(),tmp.end());
    		//for(uint j=0;j<tmp.size();++j)cout<<tmp[j]<<" ";cout<<endl;
    		assert(mp.count(tmp));
    		ans[i]=h[mp[tmp]];
    	}
    	fwt_and(ans,(1u<<(n-1)),-1);
    	for(uint i=0;i<(1u<<(n-1));++i)printf("%lld ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    内存-程序运行的空间
    数据在内存中是这样存储的(二进制形式存储)
    从编写源代码到程序在内存中运行的全过程解析
    QT开发工具
    Linux中Too many open files 问题分析和解决
    TCP端口状态说明ESTABLISHED、TIME_WAIT
    HttpClient当HTTP连接的时候出现大量CLOSE_WAIT连接
    缓存穿透、击穿、雪崩
    Http长连接和Keep-Alive以及Tcp的Keepalive
    防止表单重复提交
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12534771.html
Copyright © 2011-2022 走看看