【題解】組合數學
來自\(\texttt{SharpnessV}\)的省選複習計劃中的組合數學。
由於作者非常菜所以只能隨便寫點基礎的。
簡單數數。越獄的方案數等於總方案數減沒有越獄的方案數。
所以\(Ans=m^n-m\times (m-1)^{n-1}\) 。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 100005 #define P 100003 using namespace std; typedef long long ll; ll Pow(ll x,ll y){ ll now=1; for(;y;y>>=1,x=x*x%P)if(y&1)now=now*x%P; return now; } int main(){ ll m,n;scanf("%lld%lld",&m,&n); printf("%lld\n",(Pow(m,n)-m*Pow(m-1,n-1)%P+P)%P); return 0; }
下面是組合數環節,以下是必背恆等式。
遞推式
\[\binom{n}{m}=\binom{n-1}{m-1}+\binom {n-1}{m} \]階乘式
\[\binom{n}{m}=\dfrac{n!}{m!(n-m)!} \]對稱式
\[\binom{n}{m}=\binom{n}{n-m} \]合併式
\[\binom{n}{k}\binom{k}{m}=\binom{n}{m}\binom{n-m}{k-m} \]求和式 \(1\)
\[\sum\limits_{i=0}^n\binom{m+i}{i}=\binom{n+m+1}{n} \]求和式 \(2\)
\[\sum\limits_{i=m}^n\binom{i}{m}=\binom{n+1}{m+1} \]二項式定理
範德蒙德卷積
\[\sum\limits_{i=0}^{k}\binom{n}{i}\binom{m}{k-i}=\binom{n+m}{k} \]奇偶和相等
\[\sum\limits_{i=2k}\binom{n}{i}=\sum\limits_{i=2k+1}\binom{n}{i} \]除了等式,還需要知道二項式反演
\[f(n)=\sum\limits_{i=0}^{n}\binom{n}{i}g(i)\ \ \iff\ \ g(n)=\sum\limits_{i=0}^{n}(-1)^{n-i}\binom{n}{i}f(i) \]盧卡斯定理
直接遞推求組合數。時間複雜度\(\rm O(N^2)\),好處是不用擔心 \(P\) 不是質數。
#include<bits/stdc++.h>
using namespace std;
int t,k;
int f[2005][2005],sum[2005][2005];
void prepare(){
f[0][0]=1;
for(int i=1;i<=2000;i++){
for(int j=1;j<=2000;j++)
f[i][j]=(f[i-1][j-1]+f[i-1][j])%k;
f[i][0]=1;
}
for(int i=0;i<=2000;i++)
for(int j=0;j<=2000;j++)
if(i>=j&&!f[i][j])
sum[i][j]++;
for(int i=1;i<=2000;i++){
for(int j=1;j<=2000;j++)
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
}
int main()
{
scanf("%d%d",&t,&k);
prepare();
while(t--){
int x,y;scanf("%d%d",&x,&y);
printf("%d\n",sum[x][y]);
}
return 0;
}
\(\rm Prufer\) 序列,唯一需要知道的是一個長度為 \(n-2\) 的值域為 \(n\) 的序列與一顆有標號無根樹對應,並且是一一對應關係。
並且度數為 \(d_i\) 的節點在序列中恰好出現 \(d_i-1\) 次。
所以 \(n\) 個節點的無根樹個數是 \(n^{n-2}\) 。
這道題給定度數,就是可重集排列,直接套公式即可。
套用定理,預處理\(p\)以內的階乘,對於\(n,m\ge p\)的組合數,利用定理遞迴下去即可。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int n,m,P,fac[N],inv[N];
int Pow(int x,int y){
int now=1;
for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
return now;
}
int C(int n,int m){
if(n<m||n<0||m<0)return 0;
return 1LL*fac[n]*inv[m]*inv[n-m]%P;
}
int lucas(int n,int m){
if(n<P&&m<P)return C(n,m);
return 1LL*lucas(n/P,m/P)*lucas(n%P,m%P)%P;
}
int main(){
int T;scanf("%d",&T);
fac[0]=inv[0]=1;
while(T--){
scanf("%d%d%d",&n,&m,&P);
rep(i,1,P-1)fac[i]=1LL*fac[i-1]*i%P,inv[i]=Pow(fac[i],P-2);
printf("%d\n",lucas(n+m,n));
}
return 0;
}
非常水的基礎組合數學題。
首先我們對每個括號算貢獻,簡單容易一下得到一個括號的貢獻為可重集組合。
這裡的可重集組合是每個數都有無限個,從 \(n\) 種數種選擇 \(m\) 個數的方案數。
我們假設第 \(i\) 次選的數是 \(a_i\),不失一般性我們令 \(a_i\) 不減。
令 \(k_i=a_i+i-1\) ,不難發現 \(k_i\) 遞增,且 \(k_i\) 序列與 \(a_i\) 序列一一對應。
而 \(k_i\) 的取值範圍是 \(n+m-1\),所以可重集組合數就是\(\binom{n+m-1}{m}\)。
#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 2000005
#define P 998244353
using namespace std;
typedef long long ll;
char s[N];int n,k,sta[N],top;ll fac[N<<1],cur[N],inv[N<<1],ans;
ll Pow(ll x,int y){ll now=1;for(;y;y>>=1,x=x*x%P)if(y&1)now=now*x%P;return now;}
ll C(int x,int y){return fac[x]*inv[y]%P*inv[x-y]%P;}
void init(){
inv[0]=fac[0]=1;
rep(i,1,n+k)fac[i]=fac[i-1]*i%P,inv[i]=Pow(fac[i],P-2);
rep(i,1,n)cur[i]=C(i+k-1,k);
}
ll R(int x,int y){return (cur[x]-cur[x-y])%P;}
ll T(int x){return cur[x];}
signed main(){
scanf("%lld%lld",&n,&k);
scanf("%s",s+1),init();
for(int i=1;i<=n;i++)if(s[i]=='(')sta[++top]=i;
else ans+=1LL*R(i,i-sta[top])*T(n-i+1)%P,top=std::max(0LL,top-1);
top=0;sta[0]=n+1;
for(int i=n;i;i--)if(s[i]==')')sta[++top]=i;
else ans+=T(i)*R(n-i+1,sta[top]-i)%P,top=std::max(top-1,0LL);
printf("%lld\n",(ans%P+P)%P);
return 0;
}
錯位排列問題,\(D_n\) 表示 \(n\) 個數的錯排方案數。
我們對最後一個數 \(n\) ,它可以錯開到 \(1\sim n-1\)。
假設它錯開到 \(i\) ,那麼如果第 \(n\) 位為 \(i\) ,方案數為\(D_{n-2}\),否則方案數為 \(D_{n-1}\)。
這樣我們就得到了遞推式\(D_n=(n-1)(D_{n-1}+D_{n-2})\),或者換一種寫法\(D_n=nD_{n-1}+(-1)^n\)。
給定\(n,g\),求 \(\large g^{\sum\limits_{d\mid n}\binom{n}{d}}\bmod 999911659\)
\(999911659\)是一個質數,根據費馬小定理,我們直接求\(\sum\limits_{d\mid n}\binom{n}{d}\bmod 999911658\) 即可。
這個數顯然不是質數,但是這個數可不是亂寫的,\(999911658=2\times 3\times 4679\times 35617\),這又是四個小質數。
我們對每個質數用\(\rm Lucas\)求出\(\binom{n}{d}\),然後用 \(\rm CRT\) 合併答案即可。
首先存在長度為 $3 $ 的下降子序列的序列不是好的。
可以簡單理解一下,如果存在,則中間的元素必定要向右一次後向左一次,等於做了無用功,無法卡到下界。
那麼我們要求不存在長度為 \(3\) 的下降子序列。
我們不難\(\rm DP\),設 \(f[i][j]\) 表示前 \(i\) 個數 ,最大數為 \(j\) 的方案數。如果\(a_{i+1}>j\),則\(f[i][j]\to f[i+1][a_{i+1}]\)。否則,\(a_{i+1}\)必須為最小的數,有\(f[i][j]\to f[i+1][j]\)。
我們把 \(f[i][j]\) 看成平面直角座標系的點 \((i,j)\),我們從\((0,0)\)開始,要到\((n,n)\),每次可以直接向右移動一步,也可以向右上移動。
向右上移動看起來非常沒有規律,改一下,將向右上移動等價於先向上移動\(k\)步,再向右移動一步。
這就很清晰了,我們從原點開始,到\((n,n)\)結束,每次可以選擇向右或向上移動一步的方案數。
還有一個現實條件\(i\le j\),因為排列中 \(i\) 個數的最大值一定不小於 \(i\),在平面上,就是路徑穿過 \(y=x\) 的直線。
這就是經典組合模型,直接折線法,\(\rm Ans=\dbinom{2n}{n}-\dbinom{2n}{n-1}\) 。
考慮字典序的限制條件,類似於數位\(\rm DP\),我們前求出前\(i\)個走到的點,並計算當前點到終點的方案數。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 1200006
#define P 998244353
using namespace std;
int Pow(int x,int y){
int now=1;
for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
return now;
}
int fac[N],inv[N],n;
int C(int x,int y){
if(x<y||y<0||x<0)return 0;
return 1LL*fac[x]*inv[y]%P*inv[x-y]%P;
}
int get(int x,int y){return (C(n-x+n-y,n-x)-C(n-x+n-y,n-y-1))%P;}
int a[N],c[N];
inline void add(int x){for(;x<=n;x+=x&-x)c[x]++;}
inline int ask(int x){int sum=0;for(;x;x-=x&-x)sum+=c[x];return sum;}
void solve(){
memset(c,0,sizeof(c));
scanf("%d",&n);
rep(i,1,n)scanf("%d",&a[i]);
int y=0,ans=0;
rep(i,1,n-1){
if(a[i]<y){
ans=(ans+get(i-1,y+1))%P;
if(ask(a[i])!=a[i]-1)break;
}
else ans=(ans+get(i-1,a[i]+1))%P;
y=max(a[i],y);add(a[i]);
}
printf("%d\n",(ans+P)%P);
}
int main(){
fac[0]=inv[0]=1;
rep(i,1,N-5)fac[i]=1LL*fac[i-1]*i%P,inv[i]=Pow(fac[i],P-2);
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}
不難想到列舉匹配的情侶和他們坐的位置,有\(\dbinom{n}{k}^2k!\) 的方案數。
考慮剩下的情侶,錯排問題,定義 \(D_i\) 為 \(i\) 對情侶的錯排方案,不考慮排列順序。
經典錯排問題中\(D_i=(i-1)(D_{i-1}+D_{i-2})\) 。
這裡錯排的情侶可以男女配對,也可以同行配對,所以方案數翻倍。
\[D_i=2(i-1)(D_{i-1}+D_{i-2}) \]還考慮每對座椅分左右,再\(\times 2^n\)
總的方案數為\(\dbinom{n}{k}^2k!2^n(n-k)!D_{n-k}\),直接遞推即可。
這裡要線性複雜度,所以預處理階乘,次冪,併線性求逆元。
這樣我們不用生成函式解決了這道題。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 5000005
#define P 998244353
using namespace std;
int w = N - 5 , inv[N] , fac[N] , d[N] , pw[N];
int C(int n,int m){
if(n<0||m<0||n<m)return 0;
return 1LL*fac[n]*inv[m]%P*inv[n-m]%P;
}
int main(){
pw[0]=1;
rep(i,1,w)pw[i]=(pw[i-1]<<1)%P;
inv[1]=1;rep(i,2,w)inv[i]=-1LL*(P/i)*inv[P%i]%P;
fac[0]=inv[0]=1;
rep(i,1,w)fac[i]=1LL*fac[i-1]*i%P,inv[i]=1LL*inv[i-1]*inv[i]%P;
d[0]=1;d[1]=0;
rep(i,2,w)d[i]=1LL*2*(i-1)*(d[i-1]+d[i-2])%P;
int T;scanf("%d",&T);
while(T--){
int n,k;scanf("%d%d",&n,&k);
printf("%lld\n",1LL*C(n,k)*C(n,k)%P*fac[k]%P*pw[n]%P*d[(n-k)]%P*fac[n-k]%P);
}
return 0;
}
難度很高的一題。
首先考慮 \(a=b\) 的情況。
對於任意一種 \(A\) 贏了 \(B\) 的情況,將每次拋硬幣正負取反,對應於一種 \(B\) 贏了 \(A\) 的情況。
那麼我們將所有情況分為平局和不平局的情況,\(A\)贏了的情況顯然是不平局的情況的\(\dfrac{1}{2}\) 。
所以我們只用計算平局的情況,列舉正面向上的次數,得到\(\sum\limits_{i=0}^a \dbinom{n}{i}^2\),卷積一下得到\(\dbinom{2n}{n}\) 。
考慮 \(a>b\) 的情況。
首先對於平局\(x=y\)的情況,\(a-x>b-y\),取反後一定是 \(A\) 贏 。
對於 \(A\) 輸局 \(x<y\) 的情況,\(a-x>b-y\),取反後也一定是 \(A\) 贏。
對於 \(A\) 贏局 \(x>y\) 的情況,無法判斷 \(a-x\) 和 \(b-y\) 的大小。
我們發現前兩種情況都是輸贏對應,最後一種可能輸贏對應,也可能都是贏。
我們計算都是贏的情況,有\(a-x>b-y\to a-b>x-y\) ,考慮到\(a-b\le 10^4\),我們可以列舉 \(y\) 和 \(x-y\),令 \(k=x-y\) 。
\[\begin{aligned} sum&=\sum\limits_{k=1}^{a-b}\sum\limits_{y=0}^b\binom{a}{y+k}\binom{b}{y}\\&=\sum\limits_{k=1}^{a-b}\sum\limits_{y=0}^b\binom{a}{y+k}\binom{b}{b-y}\\&=\sum\limits_{k=1}^{a-b}\binom{a+b}{k+b}\end{aligned} \]然後直接計算組合數即可,模數不為質數,考慮擴充套件\(\rm Lucas\)。
斯特林數。
遞推式
\[\begin{Bmatrix}n\\m\end{Bmatrix}=\begin{Bmatrix}n-1\\m-1\end{Bmatrix}+m\begin{Bmatrix}n-1\\m\end{Bmatrix} \]組合意義,考慮第 \(n\) 個元素重新分組還是加入原先的組。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 105
long long s[N][N];
int main(){
s[1][1]=1;
rep(i,2,N-5)rep(j,1,i)s[i][j]=s[i-1][j]*j+s[i-1][j-1];
int n,m;
while(~scanf("%d%d",&n,&m))printf("%lld\n",s[n][m]);
return 0;
}
//這道題需要高精度
可能有用的模板。
考慮將 \(n\) 個球放入 \(m\) 個不同盒子中,允許有空盒子,得到恆等式。
\[m^n=\sum\limits_{i=0}^{m}\binom{m}{i}\begin{Bmatrix}n\\i\end{Bmatrix}i! \]令\(f(x)=x^n,g(x)=\begin{Bmatrix}n\\x\end{Bmatrix}x!\),發現上面就是個二項式反演的標準形式。一波推導後的到
\[\begin{Bmatrix}n\\i\end{Bmatrix}=\sum\limits_{i=0}^{m}\dfrac{(-1)^{m-i}i^n}{(m-i)!i!} \]發現這就是一個卷積,直接\(\rm NTT\)即可。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 600005
#define P 167772161
using namespace std;
int Pow(int x,int y){
if(y<0)return Pow(Pow(x,P-2),-y);
int now=1;
for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
return now;
}
int n,a[N],b[N],rev[N],t,fac[N];
void ntt(int *u,int op){
rep(i,0,t-1)if(rev[i]>i)swap(u[rev[i]],u[i]);
for(int l=1,k=2;k<=t;k<<=1,l<<=1){
int cur=Pow(3,(P-1)/k*op);
for(int i=0;i<t;i+=k){
int now=1;
rep(j,0,l-1){
int x=u[i+j],y=1LL*u[i+j+l]*now%P;
u[i+j]=(x+y)%P;u[i+j+l]=(x-y)%P;
now=1LL*now*cur%P;
}
}
}
}
int main(){
scanf("%d",&n);
t=1;while(t<=n*2)t<<=1;
rep(i,1,t-1)rev[i]=(rev[i>>1]>>1)|((i&1)?(t>>1):0);
fac[0]=1;rep(i,1,n)fac[i]=1LL*fac[i-1]*i%P;
rep(i,0,n)a[i]=1LL*Pow(i,n)*Pow(fac[i],P-2)%P,b[i]=Pow(-1,i)*Pow(fac[i],P-2);
ntt(a,1);ntt(b,1);rep(i,0,t-1)a[i]=1LL*a[i]*b[i]%P;ntt(a,-1);
int cur=Pow(t,-1);
rep(i,0,n)printf("%lld ",(1LL*cur*a[i]%P+P)%P);
return 0;
}
看起來多項式乘次冪乘組合數非常不可做。
一個比較討論的思路是通常冪轉下降冪。
眾所周知
\[x^n=\sum\limits_{i=0}^{n}\begin{Bmatrix}n\\i\end{Bmatrix}x^{\underline{i}} \]這裡多項式直接轉下降冪
\[\begin{aligned}Ans&=\sum\limits_{k=0}^{n}\sum\limits_{i=0}^mb_ik^{\underline{i}}\binom{n}{k}x^k\\&=\sum\limits_{k=0}^{n}\sum\limits_{i=0}^mb_in^{\underline{i}}\binom{n-i}{k-i}x^k\\&=\sum\limits_{i=0}^mb_in^{\underline{i}}x^i(x+1)^{n-i}\end{aligned} \]這裡已經可以\(\rm O(M\log M)\)計算,我們還需要計算下降冪的係數\(b_i\),直接斯特林數即可。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 1005
int P,n,m,x,a[N],b[N],s[N][N];
int Pow(int x,int y){
int now=1;
for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
return now;
}
int main(){
scanf("%d%d%d%d",&n,&x,&P,&m);
rep(i,0,m)scanf("%d",&a[i]);
s[0][0]=1;
rep(i,1,m)rep(j,1,m)s[i][j]=(s[i-1][j-1]+1LL*s[i-1][j]*j)%P;
rep(i,0,m)rep(j,i,m)b[i]=(b[i]+1LL*a[j]*s[j][i])%P;
int ans=0;
rep(i,0,m){
int cur=1;
rep(j,n-i+1,n)cur=1LL*cur*j%P;
ans=(ans+1LL*b[i]*cur%P*Pow(x,i)%P*Pow(x+1,n-i)%P)%P;
}
printf("%d\n",ans);
return 0;
}
卡特蘭數。
我們定義 \(f[i]\) 表示 \(i\) 級階梯的方案數,我們列舉矩形將階梯分成兩部分,得到兩個遞迴子問題,從而得到方程\(f[i]=\sum\limits_{j=0}^{i-1}f[j]\times f[i-j-1]\)。
這就是卡特蘭數的遞推式,一般卡特蘭數寫作\(C_i\)
\[C_n=\sum \limits_{i=0}^{n-1}C_iC_{n-i-1} \]卡特蘭數有許多經典模型:
- 給定\(1\sim n\)的入棧數列,合法的出棧序列數
- 凸\(n\)邊形的三角形劃分
- \(n\)個節點的二叉樹個數
- \(n\)對括號的合法括號序列數
這些我們都可以直接用遞推式序列,比如括號匹配,我們定義\(f_i\)表示\(n\)對括號的方案數。顯然第一個一定是左括號,我們列舉與之匹配的右括號位置,這就是卡特蘭數的遞推式。
我們可以換一個思路\(\rm DP\),定義\(f[i][j]\)表示長度為 \(i\) 的序列,剩餘 \(j\) 個左括號的方案數,顯然 \(j\ge 0\)。
在平面直角座標系中,就是從原點出發,每次可以向右上或右下走一格,走到\((2n,0)\)的方案數,不能碰到直線 \(y=-1\) 。
直接用折線法可以得到方案數,即為卡特蘭數, \(C_n=\dbinom{2n}{n}-\dbinom{2n}{n-1}\)。
把組合數拆開可以得到 \(C_n=\dfrac{(2n)!}{n!(n+1)!}\) 。
同時可以得到一階遞推公式 \(C_n=\dfrac{4n-2}{n+1}C_{n-1}\) 。
從小到大考慮每個數的填放位置。
不難發現對於奇數位和偶數位,都是從左往右填,且任何時刻奇數位填的數不少於偶數位填的數。
我們定義\(f[i][j]\)表示奇數位填了 \(i\) 個,偶數位填了 \(j\) 個。顯然\(f[i][j]\)可以轉移到\(f[i+1][j]\),\(f[i][j+1]\),且不能穿過直線 \(y=x\)。
這就是卡特蘭數的折現模型,答案為\(C_n\)。
首先 \(n\) 個節點的二叉樹有 \(C_n\)個。
我們將二叉樹的葉子刪去一個,得到 \(k\) 個大小為 \(n-1\) 的二叉樹。
反過來,我們可以在大小為 \(n-1\) 的二叉樹上掛一個節點,得到大小為 \(n\) 的二叉樹,每個二叉樹有 \(n\) 個可以掛的位置。
所以葉子數之和為 \(nC_{n-1}\),答案為\(\dfrac{nC_{n-1}}{C_n}=\dfrac{n(n+1)}{2(2n-1)}\)
#include<bits/stdc++.h>
int main(){
int n;scanf("%d",&n);
return printf("%.10lf",1.00*n*(n+1)/2/(2*n-1)),0;
}
容斥原理。
有若干個物品,每個物品有若干屬性(可能沒有),現在要求出帶有屬性的物品個數。
我們加上至少帶有一個屬性的物品,減去至少帶有兩個屬性的物品,再加上至少帶有三個,以此類推。
而最簡單的容斥就是求補集,有時候答案集合並不好求,我們直接求答案集合的補集。
簡單容斥,考慮總方案數減去不符合條件的。
不符合條件就是某個食材用了超過\(\left\lfloor\dfrac{k}{2}\right\rfloor\)次,最多隻有一個食材超過了。我們列舉是哪一個食材超過了,然後減去即可。
其實如果限制是\(\left\lfloor\dfrac{k}{3}\right\rfloor\),甚至是\(\left\lfloor\dfrac{k}{4}\right\rfloor\)都是可以做的,不妨思考一下。
硬幣種類很少,考慮容斥。
先不考慮數量限制,直接多重揹包求出方案。
然後列舉哪些硬幣超過了限制,容斥即可得到答案。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int c[4],d[4],s,n,w=N-5;long long f[N];
int main(){
rep(i,0,3)scanf("%d",&c[i]);
f[0]=1;
rep(i,0,3)rep(j,0,w-c[i])f[j+c[i]]+=f[j];
scanf("%d",&n);
rep(i,1,n){
rep(j,0,3)scanf("%d",&d[j]);
scanf("%d",&s);long long ans=f[s];
rep(S,1,15){
long long cur=0,op=1;
rep(k,0,3)if((S>>k)&1)op*=-1,cur+=(d[k]+1)*c[k];
if(cur<=s)ans+=op*f[s-cur];
}
printf("%lld\n",ans);
}
return 0;
}
求 \(n-1\) 個公司每個公司修恰好一條邊的生成樹個數。
如果不考慮什麼公司修,直接矩陣樹定理即可。
既然要考慮,直接容斥即可。把哪些公司沒有修作為容斥中物品的屬性,那麼答案就是總方案數減去有屬性的物品數量,經典容斥模型。