专栏文章

题解:AT_abc306_h [ABC306Ex] Balance Scale

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mioa4kvw
此快照首次捕获于
2025/12/02 15:51
3 个月前
此快照最后确认于
2025/12/02 15:51
3 个月前
查看原文

题目传送门

题意

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

思路

首先我们先考虑这道题的简单版本,CF1193A Amusement Park。这道题里面就是没有 u=vu=v 的情况,现在我们考虑该怎么来做。
我们注意到这种题的一个惯用套路,动态规划。我们设计 dpsdp_s 表示在 ss 状态下的方案数,ss 是一个二进制数,ss 的第 ii 位为一表示这个点在图中,否则不在。现在我们考虑怎么转移,首先给出结论 dps=dps+dpstdp_s=dp_s+dp_{s\oplus t}tst\in s,我们先不考虑重算的问题,这个转移式表示在集合 ss 中选出一个独立集 tt,以这些点为这个图中入度为 00 的点,这样就做到了定向。那为什么是独立集呢,因为如果选出的集合 tt 中有连边的话,就做不到所有点都入度为 00,就不满足转移要求了。
但是呢,很明显这样转移的话会算重,该怎么解决。很简单,如果对容斥比较熟悉的话,我们就知道奇数加,偶数减的道理,就可以很简单的解决这个算重的问题了。但是我在这里在多嘴一下,讲讲问什么是奇数加,偶数减。
借助这张图可能会好理解一些。运用二项式定理和杨辉三角即可发现,奇数加,偶数减,这样可以刚好让每一个点都被算一次且不会算重算漏。
那么具体的,我们要怎么判断独立集呢,这个可以预处理,在 ii 的状态下,如果有任何一条边的两个点都包含在这个状态下,那就不是独立集,反之则是。
现在我们考虑加上 u=vu=v 的情况,这种情况其实很好判断,我们只需要在刚才的基础上,在 ii 的状态下把有连边的点看作是同一个点,也就是等于的情况即可,因为我们在枚举独立集 tt 的时候要求其中的点两两不同,所以把 u=vu=v 的情况看作一个点就可以了,这个可以用并查集简单维护。

代码

CPP
#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;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...