现在有 \(n\) 个球,每个球有一个重量,重量未知。接下来会进行 \(m\) 次称重,每次给定 \(a_i\) 和 \(b_i\),比较这两个球的重量,结果可能是 \(>,=,<\) 中的一种。求在所有 \(3^m\) 个结果中有几种是可能出现的。
\(n\le 17,m\le n(n-1)/2\)。
技巧:怎样配容斥系数
将 \((a_i,b_i)\) 视作一条边,就是给每条边定一个方向或者定相等,要求不能成环。
思路是假装我们已经枚举那些为 \(=\) 的边了,然后要求剩下的边形成一个 DAG。而枚举 \(=\) 的边的过程可以在接下来的 DP 中顺便做。
考虑如果不允许出现 \(=\) 的话怎么做。对于一个 DAG,处理它的方法当然是依次剥掉入度为 \(0\) 的点集。重点问题在于怎样避免算重。
考虑容斥,钦定一个点集 \(T_1\) 是当前 DAG 中所有入度为 \(0\) 的点集,其中 \(T_1\) 非空。接下来要容斥,于是枚举一个 \(T\supseteq T_1\) 为实际上真正入度为 \(0\) 的点集,那么容斥系数显然为 \((-1)^{|T|-|T_1|}\)。
于是可以列出转移方程 \(f(S)=\sum_{T_1\subseteq S,T_1\neq \varnothing}\sum_{T\supseteq T_1}(-1)^{|T|-|T_1|}f(S\setminus T)\)。
然后发现这个 \(T_1\) 意义不明,于是改为枚举 \(T\subseteq S\),则合法的 \(T_1\) 数量为 \(\sum_{T_1\subseteq T,T_1\neq \varnothing}(-1)^{|T|-|T_1|}=(-1)^{|T|-1}\)。
得到 \(f(S)=\sum_{T\subseteq S,T\neq \varnothing}(-1)^{|T|-1}f(S\setminus T)\)。
然后把 \(=\) 的边加进去,发现把 \(|T|\) 改成 \(c(T)\) 即可,其中 \(c(T)\) 表示 \(T\) 中的点的导出子图的连通块个数。
最终转移方程为 \(f(S)=\sum_{T\subseteq S,T\neq \varnothing}(-1)^{c(T)-1}f(S\setminus T)\)。
时间复杂度 \(O(3^n)\)。
点击查看代码
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define Fin(file) freopen(file,"r",stdin);
#define Fout(file) freopen(file,"w",stdout);
using namespace std;
const int mod=998244353; using ll = long long;
int n,m,a[305],b[305],all,G[17],f[1<<17],cnt[1<<17];
inline void ck(int& x,int y) { x+=y-mod; x+=x>>31&mod; }
int main(){
cin>>n>>m; all=(1<<n)-1; For(i,0,n-1) G[i]|=1<<i;
For(i,1,m){
int x,y; cin>>x>>y; x--,y--; G[x]|=1<<y; G[y]|=1<<x; a[i]=x,b[i]=y;
}
For(s,0,all){
int fa[17]={0}; iota(fa,fa+n,0);
function<int(int)> getfa=[&](int x){return x==fa[x]?x:fa[x]=getfa(fa[x]);};
For(i,1,m) if((s>>a[i]&1)&&(s>>b[i]&1)) fa[getfa(b[i])]=getfa(a[i]);
For(i,0,n-1) if((s>>i&1)&&fa[i]==i) cnt[s]++;
}
f[0]=1; For(s,1,all) for(int s0=s;s0;s0=(s0-1)&s) f[s]=(f[s]+1ll*f[s^s0]*(cnt[s0]&1?1:mod-1))%mod;
cout<<f[all]<<'\n';
return 0;
}