【LOJ6072】苹果树【折半搜索】【矩阵树定理】【二项式反演】
題意:有好壞兩種點(diǎn)共 nnn 個,每個好點(diǎn)有權(quán)值,把這 nnn 個點(diǎn)連成一棵樹,一個好點(diǎn)為有用的當(dāng)且僅當(dāng)它至少與一個好點(diǎn)相鄰,求所有有用的點(diǎn)的權(quán)值和不超過 limlimlim 的方案數(shù)。
n≤40n\leq 40n≤40
這題網(wǎng)上的容斥方法基本都是假的……
發(fā)現(xiàn)至少與一個好點(diǎn)相鄰不好處理,但只能與壞點(diǎn)相鄰比較方便,所以大概是個容斥。
設(shè)有 mmm 個好點(diǎn), f(k)f(k)f(k) 表示欽定 kkk 個點(diǎn),有用的點(diǎn)只能在這 kkk 個當(dāng)中選,相當(dāng)于欽定其他 m?km-km?k 個是沒用的。(以下簡稱“可以有用”。)g(k)g(k)g(k) 表示恰好有 kkk 個有用的點(diǎn)。
那么有
f(k)=∑i=0k(ki)g(i)f(k)=\sum_{i=0}^k\binom{k}{i}g(i)f(k)=i=0∑k?(ik?)g(i)
欽定 kkk 個可以有用的點(diǎn)后,把所有好點(diǎn)和壞點(diǎn)連邊,有用的點(diǎn)和壞點(diǎn)內(nèi)部連邊,就可以算出 fff。
然后你會發(fā)現(xiàn)你假了。
第一,你算矩陣樹只能欽定固定的 kkk 個可以有用,而 fff 的定義是任意欽定,欽定這個動作本身的方案是算在其中的。
第二,因?yàn)檫@ kkk 個是不固定的,你算出了 ggg 也沒法算答案……
所以必須要改下定義。
定義 f(k)f(k)f(k) 為已經(jīng)確定了 kkk 個點(diǎn)可以有用的連邊方案。也就是具體是哪 kkk 個點(diǎn)已經(jīng)幫你欽定好了,你只需要管連邊的方案。
g(k)g(k)g(k) 為已經(jīng)確定恰好有 kkk 個點(diǎn)有用的連邊方案,具體含義同上。
先不管權(quán)值的事情,對于一個欽定可以有用的方案,建出圖后考慮它的一棵生成樹,把欽定的 kkk 個點(diǎn)中全部連的壞點(diǎn)的拿出來,假設(shè)有 iii 個,就對應(yīng)了一種 g(i)g(i)g(i) 的方案。
所以式子是一樣的
f(k)=∑i=0k(ki)g(i)f(k)=\sum_{i=0}^k\binom{k}{i}g(i)f(k)=i=0∑k?(ik?)g(i)
二項(xiàng)式反演
g(k)=∑i=0k(?1)k?i(ki)f(i)g(k)=\sum_{i=0}^k(-1)^{k-i}\binom{k}{i}f(i)g(k)=i=0∑k?(?1)k?i(ik?)f(i)
fff 因?yàn)槭蔷仃嚇渌愕?#xff0c;已經(jīng)欽定好了。對于所有 g(k)g(k)g(k),乘上欽定本身的方案的和就是答案。欽定本身可以折半搜索+two pointer算出。
復(fù)雜度 O(2n/2+n4)O(2^{n/2}+n^4)O(2n/2+n4)
順帶一提,如果是欽定沒用的點(diǎn),式子是長這樣的
f(k)=∑i=km(m?ki?k)g(i)f(k)=\sum_{i=k}^m\binom{m-k}{i-k}g(i)f(k)=i=k∑m?(i?km?k?)g(i)
而不是
f(k)=∑i=km(ik)g(i)f(k)=\sum_{i=k}^m\binom{i}{k}g(i)f(k)=i=k∑m?(ki?)g(i)
原因同上
#include <iostream> #include <cstdio> #include <cstring> #include <cctype> #include <algorithm> #include <vector> using namespace std; typedef long long ll; const int MOD=1e9+7; inline int add(const int& x,const int& y){return x+y>=MOD? x+y-MOD:x+y;} inline int qpow(int a,int p) {int ans=1;while (p){if (p&1) ans=(ll)ans*a%MOD;a=(ll)a*a%MOD,p>>=1;}return ans; } int C[45][45],n,lim,a[45],cnt[45],f[45]; vector<int> L[45],R[45]; void dfs(vector<int>* v,int cur,int pos,int k,int sum) {if (sum>lim) return;if (cur>pos) return v[k].push_back(sum);dfs(v,cur+1,pos,k,sum);dfs(v,cur+1,pos,k+1,sum+a[cur]); } inline int calc(const vector<int>& a,const vector<int>& b) {int pos=b.size(),ans=0;for (int i=0;i<(int)a.size();i++){while (pos&&a[i]+b[pos-1]>lim) --pos;ans=(ans+pos)%MOD;}return ans; } int g[45][45]; inline int det(int n) {int ans=1;for (int i=1;i<n;i++) for (int j=1;j<n;j++) (g[i][j]<0)&&(g[i][j]+=MOD);for (int i=1;i<n;i++){int pos=i;for (;!g[pos][i]&&pos<n;++pos);if (pos==n) return 0;if (pos>i) swap(g[i],g[pos]),ans=MOD-ans;ans=(ll)ans*g[i][i]%MOD;for (int j=i+1;j<n;j++){int t=(ll)g[j][i]*qpow(g[i][i],MOD-2)%MOD;for (int k=i;k<n;k++) g[j][k]=(g[j][k]-(ll)t*g[i][k]%MOD+MOD)%MOD;}}return ans; } int main() {scanf("%d%d",&n,&lim);C[0][0]=1;for (int i=1;i<=n;i++){C[i][0]=1;for (int j=1;j<=i;j++) C[i][j]=add(C[i-1][j-1],C[i-1][j]);}for (int i=1;i<=n;i++) scanf("%d",&a[i]);sort(a+1,a+n+1);int bad=0;for (;a[bad+1]==-1;++bad);int mid=(bad+1+n)>>1;dfs(L,bad+1,mid,0,0),dfs(R,mid+1,n,0,0);int m=n-bad;for (int i=0;i<=m;i++) sort(L[i].begin(),L[i].end()),sort(R[i].begin(),R[i].end());for (int k=0;k<=m;k++){for (int i=0;i<=k;i++) cnt[k]=add(cnt[k],calc(L[i],R[k-i]));memset(g,0,sizeof(g));for (int i=1;i<=bad;i++)for (int j=1;j<i;j++){++g[i][i],++g[j][j];--g[i][j],--g[j][i];}for (int i=bad+1;i<=bad+k;i++)for (int j=1;j<i;j++){++g[i][i],++g[j][j];--g[i][j],--g[j][i];} for (int i=bad+k+1;i<=n;i++)for (int j=1;j<=bad;j++){++g[i][i],++g[j][j];--g[i][j],--g[j][i]; }f[k]=det(n);}int sum=0;for (int k=0;k<=m;k++){int ans=0;for (int i=0;i<=k;i++) ans=(ans+(((i-k)&1)? -1ll:1ll)*C[k][i]*f[i])%MOD; // cerr<<ans<<'\n';sum=(sum+(ll)cnt[k]*ans)%MOD;}printf("%d\n",(sum+MOD)%MOD);return 0; }總結(jié)
以上是生活随笔為你收集整理的【LOJ6072】苹果树【折半搜索】【矩阵树定理】【二项式反演】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSP2020 赛前总结
- 下一篇: 桌面改造缺台实用风扇,这静音柔风摇头的O