图计数相关

由于我发现最近遇到了一堆图计数相关的东西,并且发现自己的技术能力太菜了,遂来学。

P6846 CEOI2019 Amusement Park

不难发现这个题目本质上要求的东西就是无向图的边定向成一个 \(\rm DAG\),最后乘上 \(\frac{m}{2}\)

这里最启发性的东西就是在 \(\rm DP\) 中发现子结构,即考虑如何从 一个 \(\rm DAG\) 变成一个子集,且这个子集也是一个 \(\rm DAG\)。不难发现由于 \(\rm DAG\) 有拓扑关系,考虑如何增量法构造出一个 \(\rm DAG\),即拓扑排序的过程,那么这个时候就不难想到,每次扣掉 \(0\) 度点即可。结合数据范围 \(n \le 18\),于是直接考虑状压 \(\rm DP\)。设 \(f_S\) 为只考虑点集 \(S\) 的导出子图定向成 \(\rm DAG\) 的方案数。

那么考虑枚举 \(0\) 度点集 \(T\),那么显然 \(T\) 必须要是一个独立集,且 \(T \to S - T\) 的无向边的方向已经确定了。于是转移就是 \(f_S = \sum_{s_0 \subset S} f_{S_0}[S_0 为独立集]\)

但是这样会算重,具体如何呢?由于有可能 \(0\) 度点集可能一次枚举不完,即一个层的独立集会被枚举多次。那么考虑容斥,容斥系数那么就是 \((-1)^{|S| - 1}\),这个东西你考虑只有一个数就可以理解了。

qwq
#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 20 + 1, M = N * N + 10, maxS = (1ll << N) + 10;
const ll mod = 998244353;

void ADD(ll &x, ll y){
    x += y; (x >= mod) ? (x -= mod) : x;
}

int n, m, con[N], pd[maxS], pc[maxS];
ll f[maxS];

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        int x, y; cin >> x >> y; x--; y--;
        con[x] |= (1ll << y); con[y] |= (1ll << x);
    } f[0] = 1;
    for(int S = 1; S < (1ll << n); S++){
        int st = 0; pc[S] = pc[S >> 1] + (S & 1);
        for(int i = 0; i < n; i++) if(S & (1ll << i)) st |= con[i];
        pd[S] = (!(S & st)); //cout << S << " " << st << " " << pd[S] << "\n";
        for(int S0 = S; S0; S0 = S & (S0 - 1)){
            if(!pd[S0]) continue; 
            //cout << S << " " << S0 << " " << f[S ^ S0] << "\n";
            ADD(f[S], f[S ^ S0] * ((pc[S0] & 1ll) ? 1ll : (mod - 1ll)) % mod);
        }
        //cout << S << " " << f[S] << "\n";
    } cout << f[(1ll << n) - 1] * m % mod * ((mod + 1) / 2) % mod;

    return 0;
}

ABC306Ex Balance Scale

这个题在上个题目中加入了一个连无向边的选择,于是扣掉的点不一定是一个独立集。考虑有一些连通块,那么显然一个联通块中的边都是无向边,可以看做一个大点。即容斥系数变成了 \((-1)^{cnt(S)}\)\(cnt(S)\)\(S\) 中的联通块中的个数。

qwq
#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back 
using namespace std;

const int N = 17 + 1, V = (1ll << N) + 10;
const ll mod = 998244353;

int n, pc[V], m, vis[N];
ll f[V];
vector<int> vec[N];

void dfs(int u, int S){
    vis[u] = 1;
    for(auto v : vec[u]) if((S & (1ll << v)) && (!vis[v])) dfs(v, S); 
}

void ADD(ll& x, ll y){x += y; (x >= mod) ? x -= mod : 0;}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        int x, y; cin >> x >> y; x--; y--;
        vec[x].pb(y); vec[y].pb(x);
    }
    for(int S = 1; S < (1ll << n); S++){
        for(int i = 0; i < n; i++) vis[i] = 0;
        for(int i = 0; i < n; i++) if(!vis[i] && (S & (1ll << i))) dfs(i, S), pc[S]++;
    } f[0] = 1;
    for(int S = 1; S < (1ll << n); S++){
        for(int S0 = S; S0; S0 = S & (S0 - 1)){
            ADD(f[S], f[S ^ S0] * ((pc[S0] & 1) ? 1ll : (mod - 1)) % mod);
        }
    } cout << f[(1ll << n) - 1];

    return 0;
}

P11714 清华集训 2014 主旋律

还是考虑记 \(f_S\) 为只考虑集合 \(S\) 的导出子图,删掉任意条边之后是强联通的方案数。但是强联通分量很难划分子结构。于是考虑求非强连通分量方案数。于是 \(f_S = all_S - \sum_{T \subset S} f_T all_{S \setminus T} \times 2^{Con(S, S \setminus T)}\)

即枚举缩点之后的零度点。但是显然这样还是会算重,其原理和 \(\rm DAG\) 计数一样。于是继续容斥,设 \(g_S\) 为类似容斥系数的东西,其实就是 \(f_S\) 的子集卷积的系数。每次合并一个 \(f_S\),就乘上一个 \(-1\)。记 \(g_S = \sum_{T \subset S} -f_Tg_{S \setminus T}\)

但是写一下发现这样还是会算重,其实问题就出在 \(g\) 的计算中,问题是每次剃掉的 \(T\) 不一定是 "1" 节点所在的点。于是强制要选某一个 \(x\) 即可。

qwq
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define pir pair<int, ll>
#define fi first
#define se second
#define pb emplace_back
#define inv(x) qpow(x, mod - 2)
using namespace std;

const int N = (1ll << 16) + 5, M = (1ll << 16) + 5;
const ll mod = 1e9 + 7;

inline void chkmin(ll& x, ll y){if(y < x) x = y;}
inline void chkmax(ll& x, ll y){if(y > x) x = y;}
inline void ADD(ll& x, ll y){x += y; (x >= mod) ? (x -= mod) : 0;}
inline void MUL(ll& x, ll y){x = x * y % mod;}

inline ll qpow(ll x, int y){
  ll ret = 1;
  for(; y; y >>= 1, MUL(x, x)) if(y & 1) MUL(ret, x);
  return ret;
}
inline int lowbit(int x){return x & -x;}

int n, m, con[M], lg[N], pc[M];
ll f[M], g[M], pw2[N], all[M];

inline int cntCon(int S, int T){
  int ret = 0;
  for(int s0 = S; s0; s0 -= lowbit(s0)) ret += pc[con[lg[lowbit(s0)]] & T];
  return ret;
}

signed main(){
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n >> m;
  for(int i = 1; i <= m; i++){
    int x, y; cin >> x >> y;
    x--; y--; con[x] |= (1ll << y);
  } pw2[0] = 1;
  for(int i = 0; i < n; i++) lg[(1ll << i)] = i;
  for(int i = 1; i < N; i++) pw2[i] = pw2[i - 1] * 2ll % mod, pc[i] = pc[i >> 1] + (i & 1);
  for(int S = 1; S < (1ll << n); S++){
    int lb = lowbit(S);
    all[S] = all[S ^ lb] + cntCon(lb, S ^ lb) + cntCon(S ^ lb, lb);
    for(int nS = S ^ lb, s0 = nS, ls = nS; ls; ls = s0, s0 = nS & (s0 - 1)) ADD(g[S], mod - f[s0 | lb] * g[nS ^ s0] % mod);
    for(int s0 = S; s0; s0 = S & (s0 - 1)) ADD(f[S], mod - pw2[cntCon(s0, S ^ s0) + all[S ^ s0]] * g[s0] % mod);
    ADD(f[S], pw2[all[S]]); ADD(g[S], f[S]);
  }
  cout << f[(1 << n) - 1];

  return 0;
}
posted @ 2025-03-04 19:52  Little_corn  阅读(25)  评论(0)    收藏  举报