LOJ #3165. 「CEOI2019」游乐园

LOJ #3165. 「CEOI2019」游乐园

对于原图上任意一种 DAG 的方案, 假设该方案翻转了 x 条边, 那么把所有边再翻转一遍仍然是个 DAG, 并且在原图的基础上翻转了 mx 条边. 若 DAG 数量为 C, 则答案 Ans=C×m2. 故可以将原问题归约为求 DAG 数量之和.

发现数据范围 1n18. 显然是状压 DP.

fS 表示点集 S 构成的导出子图为 DAG 的方案数. 接下来考虑如何转移 DP.

一个朴素的想法是枚举每一个点, 算出点 u 加入点集合 S 时的贡献. 由于 DP 的转移是有顺序的, 可以不妨设加入的点 u 在 当前 DAG 中的入度为 0. 于是点 ufS{u} 的贡献为 fS.

但是, 上述的转移会重复. 例如, 在集合 S 中添加两个互相独立的点 u,v, 则 fS{u,v} 算了两遍 fS. 于是可以考虑运用容斥原理去重.

运用容斥原理, 转移 DP 时枚举 S 的子集 T, 并且满足 T 为独立集. 则 TS 的贡献为 (1)|T|1fST. 故有转移方程

fS=TS(1)|T|1[T]fST

暴力枚举子集转移即可. 预处理独立集的复杂度为 O(2nn2), 总时间复杂度为 O(2nn2+3n).

参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int mod = 998244353;
static constexpr int Maxn = 18;
int n, m, nn;
bool e[Maxn][Maxn];
bool d[1 << Maxn];
int f[1 << Maxn];
bool parity[1 << Maxn];
int main(void) {
  scanf("%d%d", &n, &m);
  nn = (1 << n) - 1;
  for (int i = 0; i <= nn; ++i)
    parity[i] = __builtin_parity(i);
  memset(d, true, sizeof(d));
  for (int i = 0; i < m; ++i) {
    int u, v;
    scanf("%d%d", &u, &v);
    --u, --v;
    e[u][v] = e[v][u] = true;
  }
  for (int s = 0; s <= nn; ++s) {
    d[s] = true;
    for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j)
      if ((s >> i & 1) && (s >> j & 1) && e[i][j])
        d[s] = false;
  }
  f[0] = 1;
  for (int s = 1; s <= nn; ++s) {
    for (int t = s; t; t = (t - 1) & s) {
      if (d[t]) {
        f[s] += (parity[t] ? f[s ^ t] : -f[s ^ t]);
        if (f[s] < 0) f[s] += mod;
        if (f[s] >= mod) f[s] -= mod;
      }
    }
  }
  int ans = f[nn];
  ans = 1LL * ans * m % mod;
  ans = 1LL * ans * (mod + 1) / 2 % mod;
  printf("%d\n", ans);
  exit(EXIT_SUCCESS);
} // main
posted @   cutx64  阅读(160)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示