前言
继续学习容斥的技巧!
题意简介
题目大意
定义两个无重边无自环图
G
1
,
G
2
G_1,G_2
G1,G2的异或为
G
3
G_3
G3(
G
1
,
G
2
,
G
3
G_1,G_2,G_3
G1,G2,G3点数都为
n
n
n)
满足当边
(
u
,
v
)
(u,v)
(u,v)在
G
1
,
G
2
G_1,G_2
G1,G2中共出现
1
1
1次时
G
3
G_3
G3中有边
(
u
,
v
)
(u,v)
(u,v),否则没有
现在给出
s
s
s个图,集合
S
=
{
G
1
,
G
2
,
G
3
,
⋅
⋅
⋅
G
s
}
S=\{G_1,G_2,G_3,···G_s\}
S={G1,G2,G3,⋅⋅⋅Gs}问有多少个
S
S
S的子集满足所有图的异或图联通
数据范围
n ≤ 10 , s ≤ 60 n\le10,s\le 60 n≤10,s≤60
题解
模拟
模拟大概是
Θ
(
2
s
n
3
)
\Theta(2^sn^3)
Θ(2sn3)的(如果n和s的数据范围交换就能过了)
并没有什么用
正解
我们发现,直接维护是否联通并不方便
考虑容斥,记
f
(
x
)
f(x)
f(x)为有至少有
x
x
x个联通块的方案数,
g
(
x
)
g(x)
g(x)为恰好有
x
x
x个联通快的方案数
我们发现,求
f
(
x
)
f(x)
f(x)非常方便,我们只要枚举点集划分,保证一种划分中在不同的点集的点间一定没有边。我们发现,这样保证了点集之间一定不连通,但是不保证点集内部联通,所以就满足
f
(
x
)
f(x)
f(x)的定义了
我们来列出
f
(
x
)
f(x)
f(x)与
g
(
x
)
g(x)
g(x)的关系
概念:第二类斯特林数是将n个不同的元素拆分成m个集合的方法数目。
那么,就可以得出:
f
(
x
)
=
∑
i
=
x
n
{
i
x
}
g
(
i
)
f(x)=\sum_{i=x}^n\begin{Bmatrix}i\\x\end{Bmatrix}g(i)
f(x)=i=x∑n{ix}g(i)
然后就可以斯特林反演得
g
(
x
)
=
∑
i
=
x
n
(
−
1
)
i
−
x
[
i
x
]
f
(
i
)
g(x)=\sum_{i=x}^n(-1)^{i-x}\begin{bmatrix}i\\x\end{bmatrix}f(i)
g(x)=i=x∑n(−1)i−x[ix]f(i)
由于我们要求的是
g
(
1
)
g(1)
g(1)
g
(
1
)
=
∑
i
=
1
n
(
−
1
)
i
−
1
[
i
1
]
f
(
i
)
g(1)=\sum_{i=1}^n(-1)^{i-1}\begin{bmatrix}i\\1\end{bmatrix}f(i)
g(1)=i=1∑n(−1)i−1[i1]f(i)
容易发现
[
i
1
]
=
(
i
−
1
)
!
\begin{bmatrix}i\\1\end{bmatrix}=(i-1)!
[i1]=(i−1)!
所以
g
(
1
)
=
∑
i
=
1
n
(
−
1
)
i
−
1
(
i
−
1
)
!
f
(
i
)
g(1)=\sum_{i=1}^n(-1)^{i-1}(i-1)!f(i)
g(1)=i=1∑n(−1)i−1(i−1)!f(i)
我们就可以枚举点集划分,然后线性基计算方案即可(如果不会计算可以参考我的博客线性基)
贝尔数
B
n
B_n
Bn为
n
n
n个点的点集划分数量
具体
B
0
=
B
1
=
1
B_0=B_1=1
B0=B1=1,当
n
>
0
n>0
n>0时
B
n
+
1
=
∑
i
=
0
n
B
i
B_{n+1}=\sum_{i=0}^{n}B_i
Bn+1=i=0∑nBi
所以
B
10
=
115975
B_{10}=115975
B10=115975
总复杂度是
Θ
(
B
n
n
4
s
)
\Theta(B_nn^4s)
Θ(Bnn4s)由于不满,所以博主满怀信心的写了一发
然后发现TLE了,最大数据大概要跑11s+,真是非常的悲惨
原来我用的方法是
Θ
(
n
4
s
)
\Theta(n^4s)
Θ(n4s)求答案的(具体做法是把
s
s
s个长度为
n
2
n^2
n2的串放到线性基里,重要的是:我没有压位,太懒了啊),所以导致复杂度过大
事实上这题可以不用这个复杂度,因为我们发现这个
s
s
s是小于
n
2
n^2
n2的,并且我们发现很多信息都不需要维护,所以我们得到了一个更加优越的
Θ
(
n
2
s
)
\Theta(n^2s)
Θ(n2s)做法(统计每一位有哪些图符合条件来计算放进线性基里数的数量,由于此时的线性基是
s
s
s,所以更好压),那么总复杂度是
Θ
(
B
n
n
2
s
)
\Theta(B_nn^2s)
Θ(Bnn2s)、大大减小,就能过了
注:其实算法一是可以用bitset压一波的,这里列出两个算法的复杂度
算法一:
Θ
(
B
n
n
2
s
n
2
64
)
\Theta(B_nn^2s\frac{n^2}{64})
Θ(Bnn2s64n2)
算法二:
Θ
(
B
n
n
2
s
s
64
)
\Theta(B_nn^2s\frac{s}{64})
Θ(Bnn2s64s)
可见算法二复杂度更小并且更方便,所以我最后写了算法二
斯特林反演
f i = ∑ j = 0 i { i j } g j ⇔ g i = ∑ j = 0 i ( − 1 ) i − j [ i j ] f j f_i=\sum_{j=0}^i\begin{Bmatrix}i\\j\end{Bmatrix}g_j\Leftrightarrow g_i=\sum_{j=0}^i(-1)^{i-j}\begin{bmatrix}i\\j\end{bmatrix}f_j fi=j=0∑i{ij}gj⇔gi=j=0∑i(−1)i−j[ij]fj
这题用到的是一个变式:
f
i
=
∑
j
=
0
i
{
n
−
j
n
−
i
}
g
j
⇔
g
i
=
∑
j
=
0
i
(
−
1
)
i
−
j
[
n
−
j
n
−
i
]
f
j
f_i=\sum_{j=0}^i\begin{Bmatrix}{n-j}\\{n-i}\end{Bmatrix}g_j\Leftrightarrow g_i=\sum_{j=0}^i(-1)^{i-j}\begin{bmatrix}{n-j}\\{n-i}\end{bmatrix}f_j
fi=j=0∑i{n−jn−i}gj⇔gi=j=0∑i(−1)i−j[n−jn−i]fj
这个变式推一下就是题目里的那个了
证明:对于容斥原理&反演的思考和总结
代码
算法一的TLE代码,最大数据11s,正确性没有问题
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
int s,n,len;
struct Graph
{
char s[101];
}G[61],nw,zc,Base[101];
int belo[11];
LL fac[11],ans;
inline void add(Graph&a,const Graph&b)
{
for(rg int i=1;i<=len;i++)if(b.s[i]=='1')a.s[i]=a.s[i]=='0'?'1':'0';
}
inline LL build()
{
int sum=0;
for(rg int j=1;j<=s;j++)
{
Graph self=G[j];
for(rg int i=1;i<=len;i++)
if(nw.s[i]=='1'&&self.s[i]=='1')
{
if(Base[i].s[i]==0)
{
Base[i]=self,sum++;
break;
}
else add(self,Base[i]);
}
}
for(rg int i=1;i<=len;i++)if(nw.s[i]=='1')memset(Base[i].s,0,sizeof(Base[i].s));
return 1ll<<(s-sum);
}
void dfs(const int step,const int maxnum)
{
if(step==n+1)
{
int tot=0;
for(rg int i=1;i<=n;i++)
for(rg int j=i+1;j<=n;j++)
if(belo[i]!=belo[j])nw.s[++tot]='1';
else nw.s[++tot]='0';
ans+=fac[maxnum-1]*build();
return;
}
for(rg int i=1;i<=maxnum;i++)belo[step]=i,dfs(step+1,maxnum);
belo[step]=maxnum+1,dfs(step+1,maxnum+1);
}
int main()
{
fac[0]=1;
for(rg int i=1;i<=10;i++)fac[i]=-fac[i-1]*i;
read(s);
for(rg int i=1;i<=s;i++)scanf("%s",G[i].s+1);
len=strlen(G[1].s+1);
for(rg int i=1;i<=10;i++)
if(i*(i-1)/2==len)
{
n=i;
break;
}
dfs(1,0);
print(ans);
return 0;
}
贴上AC代码
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
int s,n,len;
struct Graph
{
char s[101];
}G[61];
int belo[11];
LL Base[101],top,fac[11],ans;
void dfs(const int step,const int maxnum)
{
if(step==n+1)
{
int tot=0;top=0;
for(rg int i=1;i<=n;i++)
for(rg int j=i+1;j<=n;j++)
{
tot++;
if(belo[i]!=belo[j])
{
LL zt=0,each=1;
for(rg int k=1;k<=s;k++,each<<=1)if(G[k].s[tot]=='1')zt|=each;
for(rg int k=1;k<=top;k++)if((zt^Base[k])<zt)zt^=Base[k];
if(zt)Base[++top]=zt;
}
}
ans+=fac[maxnum-1]*(1ll<<(s-top));
return;
}
for(rg int i=1;i<=maxnum;i++)belo[step]=i,dfs(step+1,maxnum);
belo[step]=maxnum+1,dfs(step+1,maxnum+1);
}
int main()
{
fac[0]=1;
for(rg int i=1;i<=10;i++)fac[i]=-fac[i-1]*i;
read(s);
for(rg int i=1;i<=s;i++)scanf("%s",G[i].s+1);
len=strlen(G[1].s+1);
for(rg int i=1;i<=10;i++)
if(i*(i-1)/2==len)
{
n=i;
break;
}
dfs(1,0);
print(ans);
return 0;
}
总结
这里这个线性基的技巧非常巧妙(甚至感觉这简直是在考线性基的相关应用),然后斯特林反演其实也算是容斥了一波,只在推式子的过程中用到(但是不得不说这样的模型不知为何用到的特别多),整道题还是非常清新的