1. 程式人生 > 其它 >組合計數

組合計數

CF838D

題意

\(n\)個位置排成一排,有\(m\)個人依次進場選位置,每個人一開始會選擇一個方向,從左到右或從右向左,並選擇一個位置,然後按他選擇的方向入場並走到這個位置,從這個位置開始繼續按他選擇的方向走,直到遇到一個空位並坐下。如果一直找不到空位,他就會生氣。求有多少種情況沒有人生氣。

\(1\leq m\leq n\leq 10^6\)

solution

看成一個長度為\(n+1\)環,其中編號\(n+1\)的點為間隔點(不能被佔據),每次每個人可以選一個起點要麼逆時針要麼順時針走到第一個空位上。如果編號為\(n+1\)的點被人佔據了顯然不合法,否則肯定合法。

由於這是一個環,因此最終每個點沒被佔據的概率是相同的。即一個點被佔據的概率是\(1-\frac{m}{n+1}\)

,乘以方案數\(2^m (n+1)^m\)即可。

CTSC2017 吉夫特

題意

給定一個長度為\(n\)的數列\(a_i\),求有多少個長度\(\ge 2\)的不上升子序列\(a_{b_1},a_{b_2},\cdots ,a_{b_k}\)滿足

\[\prod_{i=2}^k \binom {a_{b_{i-1}}} {a_{b_{i}} } > 0 \pmod 2 \]

\(1\leq n\leq 211985,1\leq a_i \leq 233333\),所有\(a_i\)互不相同。

答案對\(10^9+7\)取模。

solution

顯然根據 Lucas 定理,後一個\(a_i\)的二進位制位應是前一個的超集。

\(f_i\)表示以\(a_i\)結尾的答案,暴力的話就列舉前面所有\(a_i\)的子集轉移即可。

考慮經典的優化,設\(g_{x,y}\)表示前\(9\)位恰好為\(x\),後\(9\)位是\(y\)的子集的所有\(a_j\)對應\(f_j\)的和,那麼我們每次轉移只需要列舉\(a_i\)的前\(9\)位的子集即可。更新只需要列舉\(a_i\)\(9\)位的超集。

int a[222222];
int n;
const int B=1<<9;
const int S=B-1;
int g[555][555];
inline void add(int &x,int y){x+=y;x>=mod?x-=mod:1;}
signed main()
{
    n=read();
    R(i,1,n) a[i]=read();
    int x,y,sum;
    R(i,1,n)
    {
        x=a[i]/B,y=a[i]%B,sum=0;
        for(int t=x^S;t;t=(t-1)&(x^S)) add(sum,g[t|x][y]);
        add(sum,g[x][y]);add(sum,1);
        for(int t=y;t;t=(t-1)&y) add(g[x][t],sum);add(g[x][0],sum); 
    }
    int ans=0;
    R(i,0,S) add(ans,g[i][0]);add(ans,mod-n);
    writeln(ans);
}

Loj不等關係

題意

一個排列,相鄰兩個數有 >< 的限制,求有多少種滿足要求的排列。

\(n\leq 10^5\)

solution

< 為正向。

\(f(i,j)\)表示前\(i\)個數,當前有\(j\)個數被劃分為一組的概率。

如果\(i-1\)\(i\)< ,那麼\(f(i,j)=\frac{f(i-1,j-1)}{j}\)

否則\(f(i,1)=\sum f(i-1,j),f(i,j)=-\frac{f(i-1,j-1)}{j} (j>1)\)

最終答案自然就是\(\sum f(n,j)\)

暴力\(dp\)\(O(n^2)\)的,但是可以發現上面這個\(dp\)狀態很“浪費”,相當於強行多加了一維狀態讓轉移做到\(O(1)\)

直接令\(g(i)=\sum f(i,j)\)(即只考慮前\(i\)個數的概率),考慮如何轉移:

列舉上一次 > 被切斷是什麼時候,那麼就有:

\[g(i)=\sum_{j=1}^{i-1} \frac{[s_j='>']}{(i-j)!} g(j)(-1)^{cnt_{i-1}-cnt_{j}} \]

\(cnt\)為字首大於號數量。

後面的式子顯然是個卷積,因此可以分治NTT。

具體:

\[g(i)=(-1)^{cnt_{i-1}} \sum_{j=1}^{i-1} [s_j='>']g(j)(-1)^{cnt_j}\times \frac{1}{(i-j)!} \]

(當然也可以求解微分方程然後 exp,但是並不一定跑得過分治NTT)

時間複雜度\(O(n\log ^2 n)\)

const int _G=3;
const int invG=332748118;
char s[111111];
int n,m;
int cnt[444444];
int fac[444444],inv[444444],Finv[444444];
int G[444444];
int s1[444444],s2[444444],s3[444444];

inline void init_FAC(int lim)
{
    fac[0]=Finv[0]=inv[1]=1;
    R(i,2,lim) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    R(i,1,lim) fac[i]=1ll*fac[i-1]*i%mod,Finv[i]=1ll*Finv[i-1]*inv[i]%mod;
}
int tr[444444],tf;
inline void tpre(int n)
{
    if(tf==n) return;tf=n;
    R(i,0,n-1) tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
}
void NTT(int *g,int rev,int n)
{
    tpre(n);
    static ull f[888888],w[888888];w[0]=1;
    R(i,0,n-1) f[i]=(((ll)mod<<5)+g[tr[i]])%mod;
    for(int l=1;l<n;l<<=1)
    {
        ull tG=fpow(rev?_G:invG,(mod-1)/(l+l));
        R(i,1,l-1) w[i]=w[i-1]*tG%mod;
        for(int k=0;k<n;k+=l+l)
        {
            R(p,0,l-1) 
            {
                int tt=w[p]*f[k|l|p]%mod;
                f[k|l|p]=f[k|p]+mod-tt;
                f[k|p]+=tt;
            }
        }
        if(l==(1<<10)) R(i,0,n-1) f[i]%=mod;
    }
    if(!rev) 
    {
        ull invn=fpow(n);
        R(i,0,n-1) g[i]=f[i]%mod*invn%mod;
    }
    else R(i,0,n-1) g[i]=f[i]%mod;
}
void px(int *f,int *g,int *p,int n) {R(i,0,n-1)p[i]=1ll*f[i]*g[i]%mod;}
inline int calc(int x){return x&1?1:998244352;}
void cdq(int l,int r)
{
    if(l==r) 
    {
        if(!l) G[l]=1;
        else G[l]=1ll*G[l]*calc(cnt[l]+1)%mod;
        return;
    }
    int mid=(l+r)>>1,l_n=r-l+1,lim=1;
    cdq(l,mid);
    for(;lim<=l_n;lim<<=1);
    clr(s1,lim+10),clr(s2,lim+10),clr(s3,lim+10);
    cpy(s2,Finv,lim+5);
    R(i,l,mid) s1[i-l]=1ll*G[i]*calc(cnt[i])%mod*(s[i]=='>');
    NTT(s1,1,lim),NTT(s2,1,lim);
    px(s1,s2,s3,lim);NTT(s3,0,lim);
    R(i,mid+1,r) G[i]=(G[i]+s3[i-l])%mod;
    cdq(mid+1,r);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>(s+1);n=strlen(s+1)+1;s[0]=s[n]='>';
    init_FAC(400000);
    R(i,1,n) cnt[i]=cnt[i-1]+(s[i]=='>');
    cdq(0,n);
    cout<<1ll*fac[n]*G[n]%mod<<endl;
}

Matrix-Tree 定理

Matrix-Tree 定理的本質是對環容斥。正常容斥係數應該是\((-1)^{環個數}\)

因為是無向圖所以可以隨便欽定一個點是根節點,因此基爾霍夫矩陣去掉某個對角線元素的所在的行列答案是一樣的。而對於除了根以外的每個點都要欽定一條出邊,這樣總共欽定了\(n-1\)條邊。要求它是一棵樹的方案,就把它成環的方案減掉。最基本的想法就是容斥,容斥產生了多少個環,暴力的話就去列舉欽定產生了若干個環,剩下的點就欽定出來根之後任選出邊,容斥係數為\((-1)^{環個數}\)

然後考慮行列式的定義,相當於任取一個排列出來,把排列全部乘起來再乘上\((-1)^{逆序對個數}\)。一個定義是對於一個排列,每次交換任意兩個數字,最後交換成$1,2,3,4,5,\ldots $奇排列經歷的步數一定是奇數,偶排列定義的步數一定是偶數。而逆序對個數可以決定它是奇排列還是偶排列。

然後考慮一個排列會形成若干個置換環,然後考慮它要交換多少次才能回到初始的排列,答案即:n-環個數次。因為每個置換環都要交換環長-1次,所以要交換n-環個數次才能回到初始排列。因此最後的\((-1)\)指數上逆序對個數也可以理解為n-環個數。

基爾霍夫矩陣對角線上全部是度數。

Matrix-Tree 定理刪掉基爾霍夫矩陣中根所在的行列後,任意一個排列如果某一位選了它自己(即選在對角線上),那就說明從出邊中隨便選了一條;否則剩餘的部分會形成若干個長度\(> 1\)的置換環(事實上這些就對應了原圖的一個環(這是一個鄰接矩陣)),如果這些環形成的是奇排列則會有\(-1\)的係數。

奇排列就相當於環的總長度減掉環個數是奇數,由於非對角線元素都取了相反數,因此\((-1)\)被乘的次數恰好是環的總長度,再乘上奇排列的係數,恰好就是\((-1)^{環個數}\)

總結一下就是用行列式的定義:每次選擇一個排列,這些排列與原圖的選法一一對應,然後它乘的係數恰好就是\((-1)^{環個數}\)

因此 Matrix-Tree 定理不僅可以用於無向圖,也很容易可以推廣到任意有向圖上。

關於 Matrix-Tree 定理在有向圖上的一個例子:

現在要求內向生成樹個數。就可以將它的基爾霍夫矩陣寫出來。(對角線上都是出度,因為是選內向生成樹,每個點要欽定出邊。)這裡將根刪掉了,因為無向圖可以刪掉對角線上點對應的行列是因為每個點都可以作為根,而有向圖只能刪掉根所在的行列。

\[\begin{bmatrix} 2 & -1 & -1 \\ 0 & 1 & 0 \\ 0 & 0 & 1\end{bmatrix} \]

最後只要算一下這個矩陣的行列式即可。

LGV 引理

題意

一個 DAG ,給定\(n\)個起點\(a_1,a_2,\cdots,a_n\)\(n\)個終點\(b_1,b_2,\cdots ,b_n\),求選出\(n\)條路徑\((a_1,b_1),\cdots,(a_n,b_n)\)且這些路徑互不相交(不經過同一個點多次)的方案。

\(n\leq 500\)

solution

\(f(a,b)\)表示\(a\to b\)的方案數,那麼答案就是下面的行列式:

\[\begin{bmatrix} f(a_1,b_1) & f(a_1,b_2) & \cdots &f(a_1,b_n) \\ f(a_2,b_1) & f(a_2,b_2) & \cdots & f(a_2,b_n) \\ \vdots & \vdots & \vdots &\vdots \\f(a_n,b_1)& f(a_n,b_2) &\cdots &f(a_n,b_n) \end{bmatrix} \]

證明考慮如果最終路徑產生了\(k\)個交點,那麼可以決策這\(k\)個交點的兩條出邊分別交不交換,只要交換一次,最終的終點序列必然也會恰好交換一次,即改變排列的奇偶性。

因此若\(k>0\),則最終能夠交換得到的奇排列和偶排列的數量一樣(可以一一對應)
當且僅當\(k=0\)(即不產生交點)的時候,會被統計進答案。

模板題

https://www.luogu.com.cn/problem/P6657

題意

\(m\)個棋子,初始分別在\((a_i,0)\),最終分別要走到\((b_i,n)\),每次只能向右向上走,求路徑不交的方案數。

\(m\leq 100,n\leq 10^6\)

solution

由於每次只能向右向上走,因此是個 DAG ,直接應用 LGV 引理即可。

任意兩點之間路徑的方案數是個組合數,因此複雜度\(O(n+m^3)\)