【LOJ6072】苹果树【折半搜索】【矩阵树定理】【二项式反演】
題意:有好壞兩種點共 nnn 個,每個好點有權值,把這 nnn 個點連成一棵樹,一個好點為有用的當且僅當它至少與一個好點相鄰,求所有有用的點的權值和不超過 limlimlim 的方案數。
n≤40n\leq 40n≤40
這題網上的容斥方法基本都是假的……
發現至少與一個好點相鄰不好處理,但只能與壞點相鄰比較方便,所以大概是個容斥。
設有 mmm 個好點, f(k)f(k)f(k) 表示欽定 kkk 個點,有用的點只能在這 kkk 個當中選,相當于欽定其他 m?km-km?k 個是沒用的。(以下簡稱“可以有用”。)g(k)g(k)g(k) 表示恰好有 kkk 個有用的點。
那么有
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 個可以有用的點后,把所有好點和壞點連邊,有用的點和壞點內部連邊,就可以算出 fff。
然后你會發現你假了。
第一,你算矩陣樹只能欽定固定的 kkk 個可以有用,而 fff 的定義是任意欽定,欽定這個動作本身的方案是算在其中的。
第二,因為這 kkk 個是不固定的,你算出了 ggg 也沒法算答案……
所以必須要改下定義。
定義 f(k)f(k)f(k) 為已經確定了 kkk 個點可以有用的連邊方案。也就是具體是哪 kkk 個點已經幫你欽定好了,你只需要管連邊的方案。
g(k)g(k)g(k) 為已經確定恰好有 kkk 個點有用的連邊方案,具體含義同上。
先不管權值的事情,對于一個欽定可以有用的方案,建出圖后考慮它的一棵生成樹,把欽定的 kkk 個點中全部連的壞點的拿出來,假設有 iii 個,就對應了一種 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)
二項式反演
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 因為是矩陣樹算的,已經欽定好了。對于所有 g(k)g(k)g(k),乘上欽定本身的方案的和就是答案。欽定本身可以折半搜索+two pointer算出。
復雜度 O(2n/2+n4)O(2^{n/2}+n^4)O(2n/2+n4)
順帶一提,如果是欽定沒用的點,式子是長這樣的
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; }總結
以上是生活随笔為你收集整理的【LOJ6072】苹果树【折半搜索】【矩阵树定理】【二项式反演】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSP2020 赛前总结
- 下一篇: 桌面改造缺台实用风扇,这静音柔风摇头的O