快来踩爆这个蒟蒻吧|

Little_corn

园龄:1年1个月粉丝:11关注:17

2025-03-04 19:52阅读: 2评论: 0推荐: 0

图计数相关

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

P6846 CEOI2019 Amusement Park

不难发现这个题目本质上要求的东西就是无向图的边定向成一个 DAG,最后乘上 m2

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

那么考虑枚举 0 度点集 T,那么显然 T 必须要是一个独立集,且 TST 的无向边的方向已经确定了。于是转移就是 fS=s0SfS0[S0]

但是这样会算重,具体如何呢?由于有可能 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 主旋律

还是考虑记 fS 为只考虑集合 S 的导出子图,删掉任意条边之后是强联通的方案数。但是强联通分量很难划分子结构。于是考虑求非强连通分量方案数。于是 fS=allSTSfTallST×2Con(S,ST)

即枚举缩点之后的零度点。但是显然这样还是会算重,其原理和 DAG 计数一样。于是继续容斥,设 gS 为类似容斥系数的东西,其实就是 fS 的子集卷积的系数。每次合并一个 fS,就乘上一个 1。记 gS=TSfTgST

但是写一下发现这样还是会算重,其实问题就出在 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;
}

本文作者:Little_corn

本文链接:https://www.cnblogs.com/little-corn/p/18751278

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Little_corn  阅读(2)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起