一.题意:

你有一个有向图,你可以选择翻转一些边的方向使其变成一个DAG,求所有翻转方案的总翻转边的数量对\(998244353\)取模

二.思路:

一个很显然的暴力做法是枚举每条边是否翻转,并且通过拓扑排序判断是否存在环,复杂度为 \(O(n\times 2^m)\) 可以通过19pts的部分分。

正解

手搓几组样例,我们发现翻转的方案是成对出现的:若每次选了 \(x\) 条边翻转可以得到一个DAG,那么回到原来的图上,翻转剩余的 \(m-x\) 条边也依旧是一个DAG。

那这个猜想的正确性或者说本质是什么呢?其实是下面的定理:如果一个有向图是一个DAG,那么翻转其所有边之后,其依旧是一个DAG。 这个定理是显然成立的,那么上面的猜想其实就是因为我们先翻转了 \(x\) 条边之后变成了一个DAG,然后翻转整个图也是DAG才成立的,因为你翻转了两次,所以一开始翻转的 \(x\) 条边就被翻回到原来的状态了。所以你发现这两种情况翻转的数量加起来的和是定值 \(m\) ,所以每一次的平均贡献就是 \(\frac{m}{2}\)。

所以现在这一个问题就转化成了:给你一个无向图,需要给这个无向图定向,使其最终成为一个DAG的方案数然后答案乘上 \(\frac{m}{2}\)。

看到\(n\)的范围这么小,又是求方案数,我们就考虑状态压缩dp:定义状态 \(f(S)\) 表示给点集 \(S\) 的导出无向子图定向后为DAG的方案数。考虑转移:\(S\)中必定存在出度为\(0\)的独立集,说白了就是可以找出\(S\)的子集,使得其中的点互相没有约束(连边)。思考为什么,其实是因为一个DAG必定可以进行拓扑排序,他们是充要的,如果我们回忆拓扑排序的过程,你就会发现上面的结论是显然的。所以转移就从 \(S\) 中互相没有约束的子点集转移过来,形式化的:

\[f(S)=\sum_{T\subseteq S,T\neq\varnothing}f(S\setminus T)\cdot G(|T|)
\]

需要注意的是这里的\(T\)必须是一个独立集,并且\(G(|T|)\)是我们的容斥系数。那么现在的问题就是如何配容斥系数了。

Q:为什么会算重?

A:因为对于每一个独立集,我们可以选择一次全部加到集合 \(S\) 中,或者,分多次加到 \(S\) 中。

我们需要所有子集对于答案的最终贡献都是\(1\),但是在上述计算中,\(T\)的每一个子集都会出现,也就是我们需要:

\[\sum_{i=1}^{|T|}\dbinom{|T|}{i}G(i)=1
\]

可以证明的是:\(G(i)=(-1)^{i+1}\)。因为:

\[\sum_{i=1}^{|T|}(-1)^{i+1}\dbinom{|T|}{i}=-\left(\sum_{i=0}^{|T|}(-1)^{i}\dbinom{|T|}{i}-1\right)=-\left((-1+1)^{|T|}-1\right)=1
\]

所以最终答案就可以彻底用程序去计算了。

现在我们来分析复杂度的问题,最终计算的时候我们通过子集枚举的方式来转移dp,此部分的复杂度为 \(O(3^n)\) ,而一开始的预处理独立集的复杂度是 \(O(m\cdot2^n)\) 所以综上,总复杂度就为 \(O(3^n+m\cdot2^n)\)。此外,有的大佬题解中还包含子集卷积优化的内容,本人水平不足,就不班门弄斧了。

三.code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
return x * f;
} const int MOD = 998244353;
const int MAX_S = (1 << 18) + 5,MAX_E = 400 + 5;
int f[MAX_S],n,m,u[MAX_E],v[MAX_E],inv_2 = 499122177;
bool mark[MAX_S]; void add(int &x,int y)
{
x += y;
if(x >= MOD) x -= MOD;
if(x < 0) x += MOD;
} signed main()
{
n = read();m = read();
for(int i = 1;i <= m;++i)
{
u[i] = read();
--u[i];
v[i] = read();
--v[i];
}
int up_lim = 1 << n; for(int i = 1;i < up_lim;++i)
{
for(int j = 1;j <= m;++j)
{
if(((i >> u[j]) & 1) && ((i >> v[j]) & 1))
{
mark[i] = true;
break;
}
}
} f[0] = 1;
for(int i = 1;i < up_lim;++i)
{
for(int j = i; j;j = (j - 1) & i)
{
if(!mark[j])
{
if(__builtin_popcount(j) & 1) add(f[i],f[i ^ j]);
else add(f[i],-f[i ^ j]);
}
}
}
int ans = f[up_lim - 1] * m % MOD * inv_2 % MOD;
std::cout << ans;
return 0;
}

CF1193A Amusement Park 题解的更多相关文章

  1. CF1193A Amusement Park

    洛谷 CF1193A Amusement Park 洛谷传送门 题目翻译 有一个游乐场有一个好玩的项目:一些有向滑梯可以将游客从一个景点快速.刺激地传送到另一个景点.现在,你要帮游乐场老板来规划一个造 ...

  2. Timus 1796. Amusement Park 聪明题

    On a sunny Sunday, a group of children headed by their teacher came to an amusement park. Aunt Frosy ...

  3. URAL 1796. Amusement Park (math)

    1796. Amusement Park Time limit: 1.0 second Memory limit: 64 MB On a sunny Sunday, a group of childr ...

  4. 洛谷 AT2434 JOI 公園 (JOI Park) 题解

    人生第一次AC黑题,我太感动了. 每日一题 day31 打卡 Analysis 先跑遍DJ,求出1到 i的最短路.得到每个点到 1号点的距离后,从小到大排序一遍,这时便可以枚举每个点到 1号点的距离修 ...

  5. [codeforces 241]C. Mirror Box

    [codeforces 241]C. Mirror Box 试题描述 Mirror Box is a name of a popular game in the Iranian National Am ...

  6. UVA1599-Ideal Path(BFS进阶)

    Problem UVA1599-Ideal Path Time Limit: 3000 mSec Problem Description New labyrinth attraction is ope ...

  7. 【23.58%】【code forces 321E】Ciel and Gondolas

    time limit per test4 seconds memory limit per test512 megabytes inputstandard input outputstandard o ...

  8. 【20.00%】【codeforces 44G】Shooting Gallery

    time limit per test5 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  9. (全国多校重现赛一)D Dying light

    LsF is visiting a local amusement park with his friends, and a mirror room successfully attracts his ...

  10. NOIP前做题记录

    鉴于某些原因(主要是懒)就不一题一题写了,代码直接去\(OJ\)上看吧 CodeChef Making Change 传送门 完全没看懂题解在讲什么(一定是因为题解公式打崩的原因才不是曲明英语太差呢- ...

随机推荐

  1. centos8安装部署RADIUS+MySQLPGSQL高可用架构实现

    以下是针对中大型网络的 RADIUS+MySQL/PGSQL高可用方案 的完整实现,包含数据库集成.主备集群部署和Keepalived配置: 一.MySQL/PGSQL数据库集成(以MySQL为例) ...

  2. CVE-2021-41773 && CVE-2021-42013拆解复现

    CVE-2021-41773 && CVE-2021-42013 参考了这个师傅的WP https://www.jianshu.com/p/3076d9ec68cf CVE-2021- ...

  3. 代码随想录第九天 | 栈与队列part01

    那很好了,时间来到了第九天, 理论基础 了解一下 栈与队列的内部实现机制,文中是以C++为例讲解的. 文章讲解:https://programmercarl.com/栈与队列理论基础.html 232 ...

  4. 通过chrome插件自动生成博客评论,高效发外链

    最近crazy cattle 3d这个词爆火,很多人都在做,竞争异常激烈,甚至可以说是惨不忍睹. 从最近的数据看,胜出的主要是crazycattle3d.com, crazycattle3d.io, ...

  5. Vue 学习笔记 [Part 7]

    作者:故事我忘了¢个人微信公众号:程序猿的月光宝盒 目录 一. Promise 1.0 什么是Promise 1.1. Promise的基本使用 1.2. Promise的链式调用 1.3. Prom ...

  6. String Game

    二分答案的练手题,虽然很淼,但本题解提供一种清爽的解. 首先,二分什么: 当然是二分可以删除的次数,并使用 check 函数判断该值是否合法.这点毋庸置疑. check 怎么写. 首先,我们假设可以删 ...

  7. MyBatis实现对数据库的增删改查

    首先,整个项目的结构如图: 本次主要是对tb_brand表实现增删改查. 创建先后顺序 创建的先后顺序我在前一篇博客已经说清楚了,就不再赘述了,如果不知道如何创建的话,说明对mybatis还是不了解, ...

  8. java的StackOverflowError异常

    之前明明能查到,现在突然报错StackOverflowError,并一直在控制台返回空对象 多次遇到这种情况 发现是东西存入缓存中,缓存内存不够导致栈溢出,刷新kill缓存即可

  9. 灵活、可用、高扩展,EasyMR 带来全新 Yarn 的队列管理功能及可视化配置

    YARN(Yet Another Resource Negotiator)是 Hadoop 生态系统中的资源调度器,主要用于资源管理和作业调度.YARN 自身具备队列管理功能,通过对 YARN 资源队 ...

  10. 从Multirepo到Monorepo 袋鼠云数栈前端研发效率提升探索之路

    一.困境频生 前端代码管理何解? 前端代码管理一直是困扰不少前端开发团队的难题,从开发到发布的整体工作流程中,除了常规的技术问题外,往往还伴随着沟通成本.维护成本及协作效率等问题.这些问题在团队规模较 ...