20200516 hz 幻化成风【相等集合容斥,状态压缩】

题目描述:

在这里插入图片描述
n ≤ 1 0 4 , m ≤ 30 , ∑ a i ≤ 30 n\le10^4,m\le30,\sum{a_i}\le30 n104,m30,ai30

题目分析:

n ! n! n!拆分为质数的幂次后可以 O ( n m ) O(nm) O(nm)完全背包求出总方案数,但是这样 b i b_i bi可能相等。
考虑容斥算出 b i b_i bi互不相同的方案数,最后除以 c n t [ a [ i ] ] ! cnt[a[i]]! cnt[a[i]]!
这个容斥并不是一般的“总方案 - 满足一个性质 + 满足两个性质”那种容斥,这样算会发现有很多大小相同的相等集合时根本没法算。

需要将 m m m个数划分成 k k k个集合,强制每个集合中的数相同计算方案。但是容斥系数并不好找。
有经验的人说我们可以给每个集合 S S S一个与大小相关的容斥系数 f ∣ S ∣ f_{|S|} fS,并设一个划分的容斥系数为各个集合的容斥系数之积。
对于一个划分,如果它包含大小大于1的集合,我们希望最后相等集合恰好为这个划分的方案最后被计算的次数为0;否则我们希望它被计算的次数为1。

考虑对于一个恰好有 k k k个集合相等的方案,记为 S 1 , S 2 , . . . S_1,S_2,... S1,S2,...,在我们枚举集合划分的时候会枚举到它的子划分(子划分包含自身),那么最后相等集合恰好为这个划分的方案在枚举子划分时被计算的次数是 ∏ i = 1 k ∑ T 是 S i 的 子 划 分 T 的 容 斥 系 数 \prod_{i=1}^k\sum_{T是S_i的子划分}T的容斥系数 i=1kTSiT

我们希望对于 ∣ S ∣ > 1 |S|>1 S>1的集合 S S S,它的子划分 T T T的容斥系数之和为0,这样只要 k k k个集合中有一个大小大于1,那么它被计算的次数就是0.

据此我们可以得出 f f f的计算式: [ n = 1 ] = f n + ∑ i = 1 n ( n − 1 i − 1 ) f i ∗ [ n − i = 1 ] [n=1]=f_n+\sum_{i=1}^{n}\binom {n-1}{i-1}f_i*[n-i=1] [n=1]=fn+i=1n(i1n1)fi[ni=1]

i i i相当于是在枚举 S S S的子划分中1号点所在的集合大小。

然后就可以算了,如果暴力枚举集合划分然后背包,复杂度是 O ( B e l l ( m ) ∗ n m ) O(Bell(m)*nm) O(Bell(m)nm)的,但是我们实际上只需要知道一个集合划分中每个集合的 ∑ a i \sum a_i ai和集合大小,就可以计算背包的方案数和容斥系数。而且注意到 ∑ a i ≤ 30 \sum{a_i}\le30 ai30意味着本质不同的拆分方案远达不到 2 30 2^{30} 230,当 a i = 1 a_i=1 ai=1 30 30 30的整数拆分方案是 5604 5604 5604
所以我们用 s e t < p a i r < i n t , i n t > > set<pair<int,int>> set<pair<int,int>>来表示一个集合划分,在外层套上 m a p map map记录该划分的方案数。
但是这样 m a p map map的比较函数会很慢,所以我们需要求出集合划分的 h a s h hash hash值来 O ( 1 ) O(1) O(1)比较,于是可以自定义一个 S e t   { p a i r < i n t , i n t > a [ 30 ] , h a s h } Set~\{pair<int,int>a[30],hash\} Set {pair<int,int>a[30],hash},将 a a a数组 s o r t sort sort之后求出 h a s h hash hash值即可。

Code(令人惊奇的是代码中的 u p d upd upd(取模)函数如果改为add(int &a,int b){(a+=b)>=mod&&(a-=mod);}会慢上600ms, a a a数组预先sort会快上100ms):

#include<bits/stdc++.h>
#define maxn 31
#define fi first
#define se second
using namespace std;
const int mod = 1e9+7;
int n,m,fac[maxn],inv[maxn],a[maxn],b[maxn],e[10005],cnt,A[maxn],num,f[10005];
bool vis[10005];
typedef pair<int,int> pii;
typedef unsigned long long ULL;
ULL pw[2*maxn];
struct Set{
	pii a[maxn]; ULL s;
	Set(){memset(a,0,sizeof a),s=0;}
	void gethash(){
		sort(a,a+31,greater<pii>()),s=0;
		for(int i=0;i<30&&a[i].fi;i++) s+=a[i].fi*pw[i]+a[i].se*pw[i+30];
	}
	bool operator < (const Set &p)const{return s<p.s;}
};
map<Set,int>dp[2];//divide of set and corresponding ways.
map<Set,int>::iterator it;
map<Set,int>xs;//\sum a_i and its calc times.
void add(int &a,int b){a=(a+b)%mod;}
int upd(int a){return a>=mod?a-mod:a;}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=pw[0]=1;i<=60;i++) pw[i]=pw[i-1]*17; 
	for(int i=1;i<=m;i++) scanf("%d",&a[i]),b[a[i]]++;
	sort(a+1,a+1+m);
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=m;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=m;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
	int now=0; dp[now][Set()]=1;
	for(int i=1;i<=m;i++,now=!now){
		dp[!now].clear();
		for(it=dp[now].begin();it!=dp[now].end();++it){
			static Set tmp; int w = it->se;
			for(int j=0;j<30&&(j==0||(it->fi).a[j-1].fi);j++){
				tmp=it->fi, tmp.a[j].fi+=a[i], tmp.a[j].se++;
				tmp.gethash(), add(dp[!now][tmp],w);
			}
		}
	}
	for(it=dp[now].begin();it!=dp[now].end();++it){
		static Set tmp; tmp=it->fi; int w = it->se;
		for(int j=0;j<30&&tmp.a[j].fi;j++) w=1ll*w*fac[tmp.a[j].se-1]%mod*(tmp.a[j].se&1?1:mod-1)%mod,tmp.a[j].se=0;
		tmp.gethash(), add(xs[tmp],w);
	}
	for(int i=2;i<=n;i++) if(!vis[i]){
		++cnt; for(int j=i+i;j<=n;j+=i) vis[j]=1;
		for(int x=i;x<=n;x*=i) e[cnt]+=n/x;
	}
	int ans=0,N=e[1];
	for(it=xs.begin();it!=xs.end();++it){
		num=0; for(int i=0;i<30&&(it->fi).a[i].fi;i++) A[++num]=it->fi.a[i].fi;
		memset(f,0,(N+1)<<2),f[0]=1;
		for(int i=1;i<=num;i++)
			for(int j=A[i];j<=N;j++)
				f[j]=upd(f[j]+f[j-A[i]]);
		int ret=1;
		for(int i=1;i<=cnt;i++) ret=1ll*ret*f[e[i]]%mod;
		ans=(ans+1ll*ret*it->se)%mod;
	}
	for(int i=1;i<=30;i++) ans=1ll*ans*inv[b[i]]%mod;
	printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值