题解:AT_abc306_h [ABC306Ex] Balance Scale

题目传送门

题意

给出一个无向图定向,有三种定向方式,分别是 \(u->v\)\(v->u\)\(u=v\),要求最终的图是一个有向无环图,求出所有合法的定向图的数量。

思路

首先我们先考虑这道题的简单版本,CF1193A Amusement Park。这道题里面就是没有 \(u=v\) 的情况,现在我们考虑该怎么来做。

我们注意到这种题的一个惯用套路,动态规划。我们设计 \(dp_s\) 表示在 \(s\) 状态下的方案数,\(s\) 是一个二进制数,\(s\) 的第 \(i\) 位为一表示这个点在图中,否则不在。现在我们考虑怎么转移,首先给出结论 \(dp_s=dp_s+dp_{s\oplus t}\)\(t\in s\),我们先不考虑重算的问题,这个转移式表示在集合 \(s\) 中选出一个独立集 \(t\),以这些点为这个图中入度为 \(0\) 的点,这样就做到了定向。那为什么是独立集呢,因为如果选出的集合 \(t\) 中有连边的话,就做不到所有点都入度为 \(0\),就不满足转移要求了。

但是呢,很明显这样转移的话会算重,该怎么解决。很简单,如果对容斥比较熟悉的话,我们就知道奇数加,偶数减的道理,就可以很简单的解决这个算重的问题了。但是我在这里在多嘴一下,讲讲问什么是奇数加,偶数减。

借助这张图可能会好理解一些。运用二项式定理和杨辉三角即可发现,奇数加,偶数减,这样可以刚好让每一个点都被算一次且不会算重算漏。

那么具体的,我们要怎么判断独立集呢,这个可以预处理,在 \(i\) 的状态下,如果有任何一条边的两个点都包含在这个状态下,那就不是独立集,反之则是。

现在我们考虑加上 \(u=v\) 的情况,这种情况其实很好判断,我们只需要在刚才的基础上,在 \(i\) 的状态下把有连边的点看作是同一个点,也就是等于的情况即可,因为我们在枚举独立集 \(t\) 的时候要求其中的点两两不同,所以把 \(u=v\) 的情况看作一个点就可以了,这个可以用并查集简单维护。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2010101;
const int mod=998244353;
ll n,m,u[N],v[N],fa[N],s[N],dp[N];
ll find(ll x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++)cin>>u[i]>>v[i];
    ll k=1<<n;
    for(int i=1;i<k;i++){
        for(int j=1;j<=n;j++)fa[j]=j;
        for(int j=1;j<=m;j++){
            if((i&(1ll<<u[j]-1))&&(i&(1ll<<v[j]-1))){
                fa[find(u[j])]=find(v[j]);
            }
        }
        for(int j=1;j<=n;j++){
            if(fa[j]==j&&(i&(1ll<<j-1))){
                s[i]++;
            }
        }
    }
    dp[0]=1;
    for(int i=1;i<k;i++){
        for(int j=i;j;j=i&(j-1)){
            if(s[j]%2)dp[i]=(dp[i]+dp[i^j]+mod)%mod;
            else dp[i]=(dp[i]-dp[i^j]+mod)%mod;
        }
    }
    cout<<dp[k-1];
    return 0;
}
posted @ 2025-09-07 21:35  一班的hoko  阅读(7)  评论(0)    收藏  举报