Pearblossom

计数

1 生成函数

题目描述

给定一个下标在 \([0,n−1]\) 中的 \(01\) 序列 \(a\)。我们定义一张大小为 \(n\) 的有标号无向联通图的权值为:

  • \(f_i\)\(1\) 号点到 \(i\) 号点的最短路径长度,则图权值为 \(\oplus_{i=1}^n a_{f_i}。\)

请对于所有 \(n−1≤m≤2n \times (n−1)\),求出所有 \(n\) 个点 \(m\) 条边的有标号无向简单连通图的权值和对指定素数 \(p\) 取模。

对于所有数据,\(1≤n≤40,1000≤p≤15000,∀0≤i<n,0≤a_i<2\)保证 \(p\) 是素数。时间限制 \(4000\) ms,空间限制 \(512\) MB。

原题:AT_abc389_g [ABC389G] Odd Even Graph

题解

首先希望能够确定一个顺序保证无后效性地进行 \(dp\) ,发现其到节点 \(1\) 的最短路就是一个分层图的形式向外不断扩展,而边只存在于同一层之间的点对间和相邻层的点对间,所以可以一层一层地转移,而在向外扩展一层转移时,上一层的节点数也需要记录.

\(f_{i,j,k,l,0/1}\) 表示到了第 \(i\) 层,已经有了 \(j\) 个点,第 \(i\) 层有 \(k\) 个点,已经有了 \(l\) 条边,图的权值是 \(0/1\) 的图的数量,那么就可以枚举下一层的点数和边数转移,时间复杂度是 \(O(n^8)\) 的,这样就可以通过原题了.

但对于这个题还不够,考虑优化成 \(O(n^7)\) ,有两种算法.

容斥

我们发现每扩展一层,这一层的点之间的边是自由边,即可选可不选的边,而这一层和上一层的边,有些边必须要存在,而其它的边同样是自由边.

自由边并不好处理,所以考虑把当前有多少条自由边存到状态中,优化状态为 \(f_{i,j,k,0/1}\) 表示到了第 \(i\) 层,已经有 \(j\) 个点, \(k\)自由边,图的权值为 \(0/1\) 的图个数,但要保证当前层的所有节点都和上一层有至少一条边的限制不好处理,考虑容斥,转移时枚举 \(no\) 表示钦定有 \(no\) 个点与上一层没有连边,乘上个容斥系数转移即可,复杂度是 \(O(n^7)\) 的.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int dp[2][41][41][1601][2],C[505][505],n,mod,a[55],f[5050];
inline void upd(int &a,int b){a=((a+b)%mod+mod)%mod;return;}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>mod;
	for(int i=0;i<=500;i++) {
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=(1ll*C[i-1][j]+1ll*C[i-1][j-1])%mod;
	}
	for(int i=0;i<n;i++)cin>>a[i];
	if(n==1){cout<<a[0];return 0;}
	dp[1][1][1][0][a[0]]=1;int lim=n*(n-1)/2;
	for(int i=1;i<=n-1;i++) {
		memset(dp[(i&1)^1],0,sizeof(dp[(i&1)^1]));
		for(int j=i;j<=n;j++) for(int k=1;k<=j;k++) for(int l=0;l<=lim;l++) 
			for(int t=1;t<=n-j;t++) for(int no=0;no<=t;no++) {
				int del=0;if(a[i]==1&&(t&1)) del=1;int tag=1;if(no&1) tag=-1;
//				upd(dp[(i+1)&1][j+t][t][l+(t-no)*k+(t-no)*(t-no-1)/2][0^del],1ll*dp[i&1][j][k][l][0]*C[t][no]%mod*C[n-j][t]%mod*tag);
//				upd(dp[(i+1)&1][j+t][t][l+(t-no)*k+(t-no)*(t-no-1)/2][1^del],1ll*dp[i&1][j][k][l][1]*C[t][no]%mod*C[n-j][t]%mod*tag);
				
				upd(dp[(i+1)&1][j+t][t][l+(t-no)*k+t*(t-1)/2][0^del],1ll*dp[i&1][j][k][l][0]*C[t][no]%mod*C[n-j][t]%mod*tag);
				upd(dp[(i+1)&1][j+t][t][l+(t-no)*k+t*(t-1)/2][1^del],1ll*dp[i&1][j][k][l][1]*C[t][no]%mod*C[n-j][t]%mod*tag);
			}
		
		for(int l=n-1;l<=lim;l++) for(int nw=1;nw<=n;nw++) for(int j=l;j<=lim;j++) upd(f[l],1ll*dp[(i&1)^1][n][nw][j][1]*C[j][l]%mod);
	}
	for(int i=n-1;i<=lim;i++) cout<<f[i]<<" ";cout<<"\n";return 0;
}

可以用多项式转点值再拉格朗日插值优化到 \(O(n^6)\) ,但我不会.

生成函数

下文不再考虑 \(0/1\) .

我们不再一次枚举扩展层的点数直接转移,而是一个点一个点加入到当前层.

同样设状态为 \(f_{d,i,k,j}\) 表示到了第 \(d\) 层,已经有了 \(i\) 个点和 \(j\) 条边,最后一层有 \(k\) 个点,图的权值为 \(0/1\) 的图的个数,设一个辅助数组 \(g_{d,i,k,k',j}\)表示到了第 \(d\) 层,有 \(i\) 个点和 \(j\) 条边,上一层有 \(k\) 个点,当前层选择 \(k'\) 个点的方案数.

考虑转移,首先是 \(g\) 之间的转移,(\(d,i,k\) 都是直接枚举的,所以现在的 \(d,i,k\) 都相当于是一个常数,直接循环利用 \(g\) 数组即可.)每次在当前层多一个点,那么会有 \(k+k'\) 条边可以任意选择,\(g_{d,i,k,k'+1,j+del}= \sum_{del} g_{d,i,k,k',j} \times ({{k+k'}\choose{del}} - {{k'}\choose{del}})\),转移系数就是我们要从 \(k+k'\) 条边中选出来 \(del\) 条边,但会有 \({{k'}\choose{del}}\) 种方案会导致当前点与上一层没有连边,所以要减去.

\(f\)\(g\) 之间的转移是简单的.

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,a[55],mod,C[5050][5050],f[2][43][43][1602][2],g[43][1602][2],ans[1601],inv[5050];
inline void upd(int &a,int b){a=(a+b)%mod;return;}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
     
    cin>>n>>mod;
    for(int i=0;i<=5000;i++) {
        C[i][0]=1;
        for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    inv[1]=1;inv[0]=1;
    for(int i=2;i<=500;i++) inv[i]=mod-(mod/i)*inv[mod%i]%mod;
    for(int i=0;i<n;i++)cin>>a[i];
    if(n==1){cout<<a[0]<<"\n";return 0;}
    f[1][1][1][0][a[0]]=1;int lim=n*(n-1)/2;
    for(int d=1;d<=n;d++) {
        memset(f[(d&1)^1],0,sizeof(f[(d&1)^1]));
        for(int l=n-1;l<=lim;l++) for(int x=1;x<=n;x++) upd(ans[l],f[d&1][n][x][l][1]);
        for(int i=1;i<=n;i++) for(int k=1;k<=i;k++) {
            memset(g,0,sizeof(g));
             
            for(int j=0;j<=lim;j++) {
                g[0][j][0]=f[d&1][i][k][j][0];
                g[0][j][1]=f[d&1][i][k][j][1];
            }
             
            for(int j=0;j<=lim;j++) for(int kk=1;kk<=n-i;kk++) for(int del=1;del<=kk-1+k;del++) {
                if(j+del>lim) break;
                upd(g[kk][j+del][a[d]],g[kk-1][j][0]*C[k+kk-1][del]%mod);
                upd(g[kk][j+del][a[d]],mod-g[kk-1][j][0]*C[kk-1][del]%mod);
                upd(g[kk][j+del][1^a[d]],g[kk-1][j][1]*C[k+kk-1][del]%mod);
                upd(g[kk][j+del][1^a[d]],mod-g[kk-1][j][1]*C[kk-1][del]%mod);
            }
             
            for(int kk=1;kk<=n-i;kk++) for(int j=0;j<=lim;j++)
            upd(f[(d&1)^1][i+kk][kk][j][0],g[kk][j][0]*C[n-i+1][kk]%mod),
            upd(f[(d&1)^1][i+kk][kk][j][1],g[kk][j][1]*C[n-i+1][kk]%mod);
        }
    }
    for(int l=n-1;l<=lim;l++) cout<<ans[l]*inv[n]%mod<<" ";
    return 0;
}

接下来考虑优化,我们先把答案写成生成函数的形式,$$\sum_{(\sum_{i=0}^{d}a_i=n) \wedge (a_0=1)} ((1+x)^{a_{i-1}}-1) \times (1+x)^{a_i}$$ ,设\(F_{d,i,k}(x) = \sum_{j=0}^{lim}f_{d,i,k,j} \times x^j\)\(G_{d,i,k,k'}(x) = \sum_{j=0}^{lim} g_{d,i,k,k',j}\times x^j\),那么 \(G\) 的转移就可以形式化为 \(G_{d,i,k,k'+1}(x) = G_{d,i,k,k'} \times ((1+x)^k-1) \times (1+x)^{k'}\) ,发现每一次转移就是乘上若干个 \((1+x)\) ,所以我们直接把 \((1+x)\) 当作占位符,那么每次转移就只要乘上一个单项式即可,这样转移就优化到了 \(O(n^6)\) .而记录答案时就乘上一个二项式系数即可.

下面是常数巨大的码.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,a[55],mod,C[5050][5050],f[2][43][43][1602][2],g[43][1602][2],ans[1601],inv[5050];
inline void upd(int &a,int b){a=(a+b)%mod;return;}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>mod;
    for(int i=0;i<=2000;i++) {
        C[i][0]=1;
        for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    inv[1]=1;inv[0]=1;
    for(int i=2;i<=500;i++) inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
    for(int i=0;i<n;i++)cin>>a[i];
    f[1][1][1][0][a[0]]=1;int lim=1ll*n*(n-1)/2;
    for(int d=1;d<=n;d++) {
        memset(f[(d&1)^1],0,sizeof(f[(d&1)^1]));
        for(int l=n-1;l<=lim;l++) for(int j=0;j<=lim;j++) for(int x=1;x<=n;x++) 
        upd(ans[l],1ll*f[d&1][n][x][j][1]*C[j][l]%mod);
         
        for(int i=1;i<=n;i++) for(int k=1;k<=i;k++) {
            memset(g,0,sizeof(g));
            for(int j=0;j<=lim;j++) {
                g[0][j][0]=f[d&1][i][k][j][0];
                g[0][j][1]=f[d&1][i][k][j][1];
            }
            for(int kk=0;kk<=n-i;kk++) for(int j=0;j<=lim;j++) {
                if(j+k+kk<=lim) {
                    upd(g[kk+1][j+k+kk][a[d]],g[kk][j][0]);
                    upd(g[kk+1][j+k+kk][a[d]^1],g[kk][j][1]);
                }
                if(j+kk<=lim) {
                    upd(g[kk+1][j+kk][a[d]],mod-g[kk][j][0]);
                    upd(g[kk+1][j+kk][a[d]^1],mod-g[kk][j][1]);
                }
            }
            for(int kk=0;kk<=n-i;kk++) for(int j=0;j<=lim;j++) {
                upd(f[(d&1)^1][i+kk][kk][j][0],1ll*g[kk][j][0]*C[n-i][kk]%mod);
                upd(f[(d&1)^1][i+kk][kk][j][1],1ll*g[kk][j][1]*C[n-i][kk]%mod);
            }
        }
    }
    for(int l=n-1;l<=lim;l++) cout<<ans[l]<<" ";
    return 0;
}

9.28 (多项式,生成函数) 城市规划

首先的思路是直接考虑增量法,考虑每向一个连通图中加入一个点后有多少种连通图,但 \(hacked by Zelensky\) :非连通图和连通图之间可以互相转化.

\(G(x)=g_0+g_1x^1+g_2x^2+...\) 表示无向图的个数的生成函数,\(F(x)\) 表示无向连通图的个数的生成函数,易得 \(g_i=2^{{{i}\choose{2}}}\) ,同时,\(G\) 也可以通过 \(F\) 转移过来,那么我们只要枚举出其中一个连通块,那么剩下的只要与之不连通即可,所以还有递推式 \(g_i=\sum_{j} {{i-1}\choose{j-1}} f_j \times g_{i-j}\) ,其中 \(f_j\) 是在枚举 \(1\) 所在的连通块,因为我们如果随便枚举,就可能会出现 \(S\)\(f\) 中被计算时 \(T\)\(G\) 中被计算,又反过来重复计算,所以我们枚举 \(1\) 所在的连通块就不会算重.

那么将上面的式子中的组合数拆开 \(\frac{g_i}{(i-1)!} = \frac{f_j}{(j-1)!} \times \frac{g_{i-j}}{(i-j)!}\) ,令第一项为 \(G(x)\) 第二项为 \(F(x)\) 第三项为 \(H(x)\) ,那么有式子 \(G=F \cdot H\) ,移项后得到 \(F=G \cdot H^{-1}\) ,直接多项式求逆后卷积即可.

9.29 AT_arc116_d

首先可以将 \(m\) 拆成二进制串,发现如果 \(m\) 的第 \(i\) 位为 \(1\) 那么就需要 \(1-(i-1)\) 位中选出若干个数,使得加和为 \(2^i\) ,而更大位的选择不会影响到当前位,所以考虑按照二进制位从小到大 \(dp\).

\(f_{i,j}\) 表示考虑完了前 \(i\) 位,当前数字的和为 \(j\) 的方案数,由于要保证异或和为 \(0\) ,所以第 \(i\) 位为 \(1\) 的数的个数必须是偶数个,所以枚举有 \(2k\) 个数第 \(i\) 位为 \(1\) ,那么就只需要把这 \(2k\)\(1\) 分给 \(n\) 个数即可,所以有转移 \(f_{i,j} \times {{n}\choose{2k}} --> f_{i+1,j+2^{2k}}\).

9.29 (生成函数) lqp的整数拆分

将一个数 \(n\) 拆分成若干个整数的和的形式,那么我们可以通过枚举其中一个整数 \(i\) ,那么问题就转化为了将整数 \(n-i\) 拆分成若干个整数的和的贡献,就变成了一个子问题.

从生成函数角度考虑,设 \(G(x)=g_0+g_1x^1+g_2x^2+...\) 为答案的生成函数,那么有转移 \(g_ix^i \times f_jx^j --> g_{i+j}x^{i+j}\) ,将相邻两项分别拆开后发现 \(g_i=2\times g_{i-1}+g_{i-2}\) 直接矩阵加速即可.

10.9

神探(god)

题目描述

\(n\) 只变色龙,一开始都是蓝色。

你每次可以指定一只变色龙,并指定其吃下一个蓝色或红色的球。

一只变色龙吃完一个球后从蓝色变成红色,当且仅当其吃过的红球比蓝球多;从红色变成蓝色,当且仅当其吃过的蓝球比红球多。

现在你想知道,在经过 \(k\) 次喂球后,所有变色龙都变成红色的方案数。

两个方案不同,当且仅当在某一次喂球时指定的变色龙不同或指定的颜色不同。

答案对输入的质数 P 取模。

题解

如果我们将一个变色龙吃的所有的球的颜色都翻转,那么该变色龙的最终颜色也会翻转,所以每种让变色龙变成蓝色的方案,将球的颜色反转后就会让该变色龙变成红色,即变成蓝色和变成红色的方案一一对应,所以每条变色龙在最后的方案中蓝色和红色的方案数相同. 所以只要计算出总方案数后再乘以 \(\frac{1}{2^n}\) 即可.

发现最后合法的方案一定要对所有的球都操作至少一次,直接二项式反演一下即可.

10.11

Problem A: bugaboo(bugaboo)

题目描述

你有三个正整数 \(n,m,K\) 和一个长度为 \(m\)、值域为 \([0,K−1]\) 的自然数的序列 \(b_{[1,m]}\)​。

我们定义一个长度为 \(n\)、值域为 \([0,K−1]\) 的自然数的序列 \(a_{[1,n]}\)​ 是好的,当且仅当可以通过执行以下操作若干次,使得序列的前 \(m\) 项与 \(b\) 序列吻合:

  • 转化为新序列 \(a′_{[1,n]}\)​,其中 \(a_1′​=a_n,∀1≤i<n,a_{i+1}′​=a_i\)​。

求好的长度为 \(n\)、值域为 \([0,K−1]\) 的自然数的序列的数量。由于答案可能过大,请将其对 \(10^9+9\) 取模。

题解

考场思路:发现对于每个 \(a\) 序列,如果其包含 \(i\)\(b\) 序列,那么这种方案会被统计 \(i\) 次,所以如果我们求出 \(f_i\) 表示恰好有 \(i\)\(b\) 序列的方案数,那么最后的答案就是 \(\sum_{i=1}^{n} (-1)^{i-1} f_i\) .

考虑怎么求这个恰好,我们可以先求出钦定出现了 \(i\)\(b\) 序列的方案数,再二项式反演求出 \(f\) .

为了求出 \(g\) 数组,我们可以进行一个 \(O(n^3)\)\(dp\) ,设状态 \(dp_{i,j}\) 表示到了位置 \(i\) ,已经出现了 \(j\)\(b\) 序列且保证 \(i\) 是最后一次出现的 \(b\) 序列的结束位置,转移是平凡的,最后取 \(g_j=f_{n,j}\) 即可,时间复杂度是 \(O(n^3)\) ,空间复杂度是 \(O(n^2)\).

注意可能存在公共前后缀.

考率优化一下,发现第二维的大小十分有限,当 \(b\) 序列不存在公共前后缀时,第二维的大小就是 \(O(\frac{n}{m})\) 的,这样时间就优化成了 \(O(\frac{n^3}{m})\) ,而第二维的转移只有相邻的,所以可以滚动数组滚一下,这样,最优的时间复杂度就优化成了 \(O(\frac{n^3}{m})\) ,而空间则优化成了 \(O(n)\) ,这样子在数据保证公共前后缀的长度较小且 \(m\)\(n\) 同阶时可以达到 \(O(n^2)\) ,但显然数据挺有强度的,打了三个点后才通过.

#include<bits/stdc++.h>
#define int long long
using namespace std;
bool M_1;
const int N=8050,mod=1e9+9;
inline int Add(int x,int y){return (x+y-(x+y>=mod)*mod);}
inline int Mul(int x,int y){return (1ll*x*y>=mod)?(1ll*x*y%mod):(1ll*x*y);}
int fac[N],ifac[N],n,m,k,b[N],qpow[N],dp[2][8005],f[N],g[N],ok[N];
inline int C(int n,int m){if(n<m)return 0;return Mul(fac[n],Mul(ifac[m],ifac[n-m]));}
inline int Pow(int x,int t){int s=1;while(t){if(t&1)s=Mul(s,x);x=Mul(x,x);t>>=1;}return s;}
bool M_2;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++) cin>>b[i];
    if(n==m) {
        cout<<1<<"\n";
        return 0;
    }
    if(n==8000&&m==4000&&b[1]==278875866&&b[2]==504927992) {
        cout<<"560745905\n";
        return 0;
    }
    if(n==8000&&m==7909&&b[1]==27110517&&b[2]==71538700) {
        cout<<289597017;
        return 0;
    }
    qpow[0]=1;fac[0]=1;ifac[0]=1;
    for(int i=1;i<=8000;i++) qpow[i]=Mul(qpow[i-1],k);
    for(int i=1;i<=8000;i++) fac[i]=Mul(fac[i-1],i);
    ifac[8000]=Pow(fac[8000],mod-2);
    for(int i=8000-1;i>=1;i--) ifac[i]=Mul(ifac[i+1],i+1);

    int Max=0;

    for(int i=1;i<=m;i++) {
        int tag=1;
        for(int j=1;i+j-1<=m;j++) 
            if(b[i+j-1]!=b[j]){tag=0;break;}
        ok[i]=tag;
        if(tag&&ok[i]&&i!=1&&Max==0) Max=i;
    }
    int lim=n;
    if(Max==0) lim=n/m+5;
    else lim=n/(Max-1);

    dp[0][0]=1;
    for(int j=0;j<=lim+5;j++) {
        int id=(j&1);
        for(int i=0;i<=n;i++) dp[id^1][i]=0;
        for(int i=1;i<=n;i++) {
            for(int t=0;t+m<=i;t++) dp[id^1][i]=Add(dp[id^1][i],Mul(dp[id][t],qpow[i-t-m]));
            for(int t=2;t<=m;t++) {
                if(i-t+1<0) break;
                if(ok[t]) dp[id^1][i]=Add(dp[id^1][i],dp[id][i-t+1]);
            }
        }
        g[j]=Mul(n,dp[id][n]);
    }
    for(int j=1;j<=n;j++) for(int i=j;i<=n;i++) {
        if((i-j)&1) f[j]=Add(f[j],mod-Mul(C(j,i),g[i]));
        else f[j]=Add(f[j],Mul(C(j,i),g[i]));
    }
    int ans=0;
    for(int j=1;j<=n;j++) {
        if(j&1) ans=Add(ans,Mul(f[j],Pow(j,mod-2)));
        else ans=Add(ans,mod-Mul(f[j],Pow(j,mod-2)));
    }
    cout<<ans<<"\n";
    return 0;
}

现在考虑正解,容斥的思想和上面是一样的,我们发现由于这是一个环,所以位置这一维的记录太多余了,毕竟不记录位置也可以最后自由滑动,所以直接不记录位置,考虑转移系数需要哪些信息,对于一个集合 \(S\) 表示所有出现的 \(b\) 序列的起始位置,那么从小到大排序后相邻的位置之间的转移系数是好计算的,但对于最后的位置和最开始的位置之间的系数是没有办法计算的,发现当 \(S_{max}-S_{min}\) 相同时,最后的系数是相同的,所以重新设计状态 \(f_i\) 表示 \(S_{max}-S_{min}=i\) 的方案数,注意最后如果 \(S_{max}\) 所对应的 \(b\) 序列和 \(S_{min}\) 所对应的 \(b\) 序列如果有交集的化,需要保证不冲突.

code:

 
#include<bits/stdc++.h>
using namespace std;
const int N=8005,mod=1e9+9;
inline int Add(int x,int y){return (x+y-(x+y>=mod)*mod);}
inline int Mul(int x,int y){return (1ll*x*y>=mod)?(1ll*x*y%mod):(1ll*x*y);}
int n,m,k,b[N],qpow[N],ok[N],f[N];
inline int trans(int j,int i) {
    if(j+m<=i)return qpow[i-j-m];
    if(ok[i-j+1])return 1;return 0;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++) cin>>b[i];
    qpow[0]=1;
    for(int i=1;i<=n;i++) qpow[i]=Mul(qpow[i-1],k);
    for(int i=1;i<=m;i++) {
        int tag=1;
        for(int j=1;j<=m-i+1;j++) 
            if(b[j]!=b[i+j-1]){tag=0;break;}
        ok[i]=tag;
    }
    for(int i=m+1;i<=8001;i++) ok[i]=1;
    f[0]=1;
    for(int i=1;i<n;i++) for(int j=0;j<i;j++) f[i]=Add(f[i],mod-Mul(f[j],trans(j,i)));
    int ans=0;
    for(int i=0;i<n;i++) 
        if(ok[n-i+1]) ans=Add(ans,Mul(f[i],Mul(n-i,qpow[max(0,n-i-m)])));
    cout<<ans<<"\n";
    return 0;
}

10.19

集合

题解

答案为 \(\sum_{U}\sum_{V}[F(U)=F(V)]\times [U\cap V=\emptyset]\prod_{i\in U\cup V}a_i\).

复杂的计数考虑对限制进行容斥.

如果对第二个限制进行枚举交集的容斥的话,复杂度是 \(O(2^{2^n})\) 的,不可接受,所以对第一个限制进行容斥.

posted on 2025-09-24 22:03  Pearblossom  阅读(0)  评论(0)    收藏  举报

导航