20200516 hz 幻化成風【相等集合容斥,狀態壓縮】

題目描述:

在這裏插入圖片描述
n104,m30,ai30n\le10^4,m\le30,\sum{a_i}\le30

題目分析:

n!n!拆分爲質數的冪次後可以O(nm)O(nm)完全揹包求出總方案數,但是這樣bib_i可能相等。
考慮容斥算出bib_i互不相同的方案數,最後除以cnt[a[i]]!cnt[a[i]]!
這個容斥並不是一般的“總方案 - 滿足一個性質 + 滿足兩個性質”那種容斥,這樣算會發現有很多大小相同的相等集合時根本沒法算。

需要將mm個數劃分成kk個集合,強制每個集合中的數相同計算方案。但是容斥係數並不好找。
有經驗的人說我們可以給每個集合SS一個與大小相關的容斥係數fSf_{|S|},並設一個劃分的容斥係數爲各個集合的容斥係數之積。
對於一個劃分,如果它包含大小大於1的集合,我們希望最後相等集合恰好爲這個劃分的方案最後被計算的次數爲0;否則我們希望它被計算的次數爲1。

考慮對於一個恰好有kk個集合相等的方案,記爲S1,S2,...S_1,S_2,...,在我們枚舉集合劃分的時候會枚舉到它的子劃分(子劃分包含自身),那麼最後相等集合恰好爲這個劃分的方案在枚舉子劃分時被計算的次數是i=1kTSiT\prod_{i=1}^k\sum_{T是S_i的子劃分}T的容斥係數

我們希望對於S>1|S|>1的集合SS,它的子劃分TT的容斥係數之和爲0,這樣只要kk個集合中有一個大小大於1,那麼它被計算的次數就是0.

據此我們可以得出ff的計算式:[n=1]=fn+i=1n(n1i1)fi[ni=1][n=1]=f_n+\sum_{i=1}^{n}\binom {n-1}{i-1}f_i*[n-i=1]

ii相當於是在枚舉SS的子劃分中1號點所在的集合大小。

然後就可以算了,如果暴力枚舉集合劃分然後揹包,複雜度是O(Bell(m)nm)O(Bell(m)*nm)的,但是我們實際上只需要知道一個集合劃分中每個集合的ai\sum a_i和集合大小,就可以計算揹包的方案數和容斥係數。而且注意到ai30\sum{a_i}\le30意味着本質不同的拆分方案遠達不到2302^{30},當ai=1a_i=13030的整數拆分方案是56045604
所以我們用set<pair<int,int>>set<pair<int,int>>來表示一個集合劃分,在外層套上mapmap記錄該劃分的方案數。
但是這樣mapmap的比較函數會很慢,所以我們需要求出集合劃分的hashhash值來O(1)O(1)比較,於是可以自定義一個Set {pair<int,int>a[30],hash}Set~\{pair<int,int>a[30],hash\},將aa數組sortsort之後求出hashhash值即可。

Code(令人驚奇的是代碼中的updupd(取模)函數如果改爲add(int &a,int b){(a+=b)>=mod&&(a-=mod);}會慢上600ms,aa數組預先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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章