题面传送门

神仙虚树题。

首先考虑最 trival 的情况:\(m=n-1\),也就是一棵树的情况。这个我相信刚学树形 \(dp\) 的都能够秒掉罢(确信)。直接设 \(dp_{i,0/1}\) 在表示 \(i\) 的子树内选择,\(i\) 选/不选的方案数。转移就 \(dp_{u,0}=\prod\limits_{v\in son_u}(dp_{v,0}+dp_{v,1}),dp_{u,1}=\prod\limits_{v\in son_u}dp_{v,0}\) 即可。

接下来考虑有非树边的情况,首先我们一遍 DFS 找出所有非树边组成的集合 \(E_t\)(显然 \(|E_t|=m-n+1\))注意到 \(m\le n+10\),故 \(|E_t|\le 11\),我们考虑暴力枚举每条非树边的选择情况——共有 \((0,0),(0,1),(1,0)\) 三种情况,但实际上我们发现如果左端点不选,那右端点就没有限制了,因此我们只需枚举左端点的情况,我们维护一个 \(ban_{u,x}\) 表示点 \(u\) 选/不选是否不可行,那么状态转移方程需改为 \(dp_{u,0}=\prod\limits_{v\in son_u}(dp_{v,0}+dp_{v,1})\times(1-ban_{u,0}),dp_{u,1}=\prod\limits_{v\in son_u}dp_{v,0}\times(1-ban_{u,1})\)。这样复杂度是 \(n2^{|E_t|}\) 的,大约可以拿到 \(75\) 分的好成绩。

考虑进一步优化,注意到虽然状态数很多,但满足 \(ban_{u,0}=1\lor ban_{u,1}=1\) 的点并不多,最多只有 \(22\) 个,因此我们考虑对这些关键点建立虚树,但是由于在建立虚树的过程中我们将有的链缩成了边,因此我们就不能再像之前那样转移了,我们考虑对每条虚树上的边 \(e=(u,v)\)(\(u\) 是 \(v\) 的父亲)预处理一个系数 \(k_{e,0/1,0/1}\),表示 \(dp_{u,0}=\prod\limits_{e\in E_u}(dp_{v,0}\times k_{e,0,0}+dp_{v,1}\times k_{e,0,1}),dp_{u,1}=\prod\limits_{e\in E_u}(dp_{v,0}\times k_{e,1,0}+dp_{v,1}\times k_{e,1,1})\)(有点类似于矩阵乘法),其中 \(E_u\) 为以 \(u\) 为上端节点的虚链的集合,显然如果我们求出了 \(k_{e,0/1,0/1}\) 就可以直接在虚树上 \(dp\) 了,时间复杂度也就降到了 \(|E_t|\times 2^{|E_t|}\)。

于是现在我们的任务就是求出 \(k_{e,0/1,0/1}\),首先对于每个虚树边 \((u,v)\) 的对应链上的点 \(w\),\(w\) 子树内距离 \(w\) 最近的在虚树上的点是唯一的,这个点就是 \(v\),因此我们再建一个数组 \(dk_{w,0/1,0/1}\) 表示 \(dp_{w,0}=dp_{v,0}\times dk_{w,0,0}+dp_{v,1}\times dk_{w,0,1},dp_{w,1}=dp_{v,0}\times dk_{w,1,0}+dp_{v,1}\times dk_{w,1,1}\),显然对于关键点 \(u\),\(dp_{u,0,0}=dp_{u,1,1}=1,dp_{u,1,0}=dp_{u,0,1}=0\)。考虑怎样通过 \(w\) 子树的 \(dk\) 求出 \(dk_w\),显然对于 \(w\) 的所有儿子,有且只有一个在 \(u\to v\) 这条虚链上,我们假设这个点为 \(x\),那么对于其他的 \(w\) 的儿子 \(y\ne x\),\(dp_{y,0},dp_{y,1}\) 显然是定值,根据前面的状态转移方程是直接乘上即可,即 \(dk_{w,0,0/1}\) 乘上 \(dp_{y,0}+dp_{y,1}\),\(dk_{w,1,0/1}\) 乘上 \(dp_{y,0}\)。而对于 \(w\) 在关键链上的儿子,根据 \(dp_{x,0}=dp_{v,0}\times dk_{x,0,0}+dp_{v,1}\times dk_{x,0,1},dp_{x,1}=dp_{v,0}\times dk_{x,1,0}+dp_{v,1}\times dk_{x,1,1}\),可以得出 \(x\) 对 \(dp_{w,0}\) 的贡献是 \(dp_{x,0}+dp_{x,1}=dp_{v,0}\times(dk_{x,0,0}+dk_{x,1,0})+dp_{v,1}\times(dk_{x,0,1}+dk_{x,1,1})\),对 \(dp_{w,1}\) 的贡献是 \(dp_{v,0}\times dk_{x,0,0}+dp_{v,1}\times dk_{x,0,1}\),故我们需令 \(dk_{w,0,0}\leftarrow dk_{w,0,0}\times(dk_{x,0,0}+dk_{x,1,0}),dk_{w,1,0}\leftarrow dk_{w,1,0}\times dk_{x,0,0}\),这样即可求出 \(k_{e,0/1,0/1}\),具体见代码罢。

  1. const int MAXN=1e5+10;
  2. const int MAXK=11;
  3. const int MOD=998244353;
  4. int n,m,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1,ans=0;
  5. bool ist[MAXN+5],onv[MAXN+5];
  6. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  7. int dfn[MAXN+5],tim=0;pii et[MAXK+5];int ecnt=0;
  8. void dfs1(int x,int f){
  9. dfn[x]=++tim;
  10. for(int e=hd[x];e;e=nxt[e]){
  11. int y=to[e];if(y==f) continue;
  12. if(!dfn[y]) dfs1(y,x);
  13. else{
  14. onv[x]=onv[y]=1;ist[e>>1]=1;
  15. if(dfn[x]<dfn[y]) et[++ecnt]=mp(x,y);
  16. }
  17. }
  18. }
  19. int cnt[MAXN+5];
  20. void dfs2(int x,int f){
  21. for(int e=hd[x];e;e=nxt[e]){
  22. int y=to[e];if(y==f||ist[e>>1]) continue;
  23. dfs2(y,x);cnt[x]+=cnt[y];
  24. } onv[x]|=(cnt[x]>=2);cnt[x]=!!(cnt[x]+onv[x]);
  25. }
  26. pii k[MAXN+5][2];int dp0[MAXN+5][2];
  27. vector<pair<int,pair<pii,pii> > > g[MAXN+5];
  28. pii operator +(pii lhs,pii rhs){return mp((lhs.fi+rhs.fi)%MOD,(lhs.se+rhs.se)%MOD);}
  29. pii operator *(pii lhs,int rhs){return mp(1ll*lhs.fi*rhs%MOD,1ll*lhs.se*rhs%MOD);}
  30. int dfs3(int x,int f){
  31. dp0[x][0]=dp0[x][1]=1;int z=0;
  32. for(int e=hd[x];e;e=nxt[e]){
  33. int y=to[e];if(y==f||ist[e>>1]) continue;
  34. int dw=dfs3(y,x);
  35. if(!dw){
  36. dp0[x][0]=1ll*dp0[x][0]*(dp0[y][0]+dp0[y][1])%MOD;
  37. dp0[x][1]=1ll*dp0[x][1]*dp0[y][0]%MOD;
  38. } else if(onv[x]) g[x].pb(mp(dw,mp(k[y][0]+k[y][1],k[y][0])));
  39. else k[x][0]=k[y][0]+k[y][1],k[x][1]=k[y][0],z=dw;
  40. } if(onv[x]) k[x][0]=mp(1,0),k[x][1]=mp(0,1),z=x;
  41. else k[x][0]=k[x][0]*dp0[x][0],k[x][1]=k[x][1]*dp0[x][1];
  42. // printf("%d %d %d %d %d\n",x,k[x][0].fi,k[x][0].se,k[x][1].fi,k[x][1].se);
  43. // printf("%d %d\n",dp0[x][0],dp0[x][1]);
  44. return z;
  45. }
  46. bool ban[MAXN+5][2];int dp[MAXN+5][2];
  47. void dfs4(int x){
  48. dp[x][0]=(!ban[x][0])*dp0[x][0];
  49. dp[x][1]=(!ban[x][1])*dp0[x][1];
  50. for(int i=0;i<g[x].size();i++){
  51. int y=g[x][i].fi;dfs4(y);
  52. pii p0=g[x][i].se.fi,p1=g[x][i].se.se;
  53. dp[x][0]=1ll*dp[x][0]*((1ll*dp[y][0]*p0.fi+1ll*dp[y][1]*p0.se)%MOD)%MOD;
  54. dp[x][1]=1ll*dp[x][1]*((1ll*dp[y][0]*p1.fi+1ll*dp[y][1]*p1.se)%MOD)%MOD;
  55. } //printf("%d %d %d\n",x,dp[x][0],dp[x][1]);
  56. }
  57. int main(){
  58. scanf("%d%d",&n,&m);
  59. for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
  60. dfs1(1,0);dfs2(1,0);onv[1]=1;dfs3(1,0);
  61. // for(int i=1;i<=n;i++) printf("%d%c",onv[i]," \n"[i==n]);
  62. // for(int i=1;i<=ecnt;i++) printf("%d %d\n",et[i].fi,et[i].se);
  63. for(int i=0;i<(1<<ecnt);i++){
  64. for(int j=1;j<=ecnt;j++){
  65. if(i>>(j-1)&1) ban[et[j].fi][0]=1,ban[et[j].se][1]=1;
  66. else ban[et[j].fi][1]=1;
  67. } dfs4(1);ans=(ans+dp[1][0])%MOD;ans=(ans+dp[1][1])%MOD;
  68. for(int j=1;j<=ecnt;j++){
  69. if(i>>(j-1)&1) ban[et[j].fi][0]=0,ban[et[j].se][1]=0;
  70. else ban[et[j].fi][1]=0;
  71. }
  72. } printf("%d\n",ans);
  73. return 0;
  74. }

洛谷 P4426 - [HNOI/AHOI2018]毒瘤(虚树+dp)的更多相关文章

  1. 洛谷P2495 [SDOI2011]消耗战(虚树dp)

    P2495 [SDOI2011]消耗战 题目链接 题解: 虚树\(dp\)入门题吧.虚树的核心思想其实就是每次只保留关键点,因为关键点的dfs序的相对大小顺序和原来的树中结点dfs序的相对大小顺序都是 ...

  2. 洛谷P4425 [HNOI/AHOI2018]转盘(线段树)

    题意 题目链接 Sol 首先猜一个结论:对于每次询问,枚举一个起点然后不断等到某个点出现时才走到下一个点一定是最优的. 证明不会,考场上拍了3w组没错应该就是对的吧... 首先把数组倍长一下方便枚举起 ...

  3. 洛谷 P2495 [SDOI2011]消耗战(虚树,dp)

    题面 洛谷 题解 虚树+dp 关于虚树 了解一下 具体实现 inline void insert(int x) { if (top == 1) {s[++top] = x; return ;} int ...

  4. [BZOJ5287][HNOI2018]毒瘤(虚树DP)

    暴力枚举非树边取值做DP可得75. 注意到每次枚举出一个容斥状态的时候,都要做大量重复操作. 建立虚树,预处理出虚树上两点间的转移系数.也可动态DP解决. 树上倍增.动态DP.虚树DP似乎是这种问题的 ...

  5. [Bzoj5285][洛谷P4424][HNOI/AHOI2018]寻宝游戏(bitset)

    P4424 [HNOI/AHOI2018]寻宝游戏 某大学每年都会有一次Mystery Hunt的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为新生 ...

  6. 洛谷P4103 [HEOI2014]大工程(虚树 树形dp)

    题意 链接 Sol 虚树. 首先建出虚树,然后直接树形dp就行了. 最大最小值直接维护子树内到该节点的最大值,然后合并两棵子树的时候更新一下答案. 任意两点的路径和可以考虑每条边两边的贡献,\(d[x ...

  7. bzoj 3611(洛谷 4103) [Heoi2014]大工程——虚树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3611 https://www.luogu.org/problemnew/show/P4103 ...

  8. 洛谷P2495 [SDOI2011]消耗战(虚树)

    题面 传送门 题解 为啥一直莫名其妙\(90\)分啊--重构了一下代码才\(A\)掉-- 先考虑直接\(dp\)怎么做 树形\(dp\)的时候,记一下断开某个节点的最小值,就是从根节点到它的路径上最短 ...

  9. Luogu P4426 [HNOI/AHOI2018]毒瘤

    题目 神仙题. 首先我们可以把题意转化为图的独立集计数.显然这个东西是个NP-Hard的. 然后我们可以注意到\(m\le n+10\),也就是说最多有\(11\)条非树边. 我们现在先考虑一下,树上 ...

随机推荐

  1. Unity——EasyTouch摇杆插件使用

    EasyTouch摇杆插件使用 Demo展示 双指缩放在电脑端无法掩饰,竖屏将就看看吧: 插件名叫EasyTouch,有需要给我留言,不想开仓库传了: 创建摇杆点这里: 初始化 On_Joystick ...

  2. 欧姆龙plc通讯协议格式

    欧姆龙CPM1A型plc与上位计算机通信的顺序是上位机先发出命令信息给PLC,PLC返回响应信息给上位 机.每次通信发送/接受的一组数据称为一"帧".帧由少于131个字符的数据构成 ...

  3. 2021.8.19考试总结[NOIP模拟44]

    T1 emotional flutter 把脚长合到黑条中. 每个黑条可以映射到统一区间,实际操作就是左右端点取模.长度大于$k$时显然不合法. 然后检查一遍区间内有没有不被黑条覆盖的点即可. 区间端 ...

  4. hdu 5093 Battle ships(二分图最大匹配)

    题意: M*N的矩阵,每个格子上是三个之一:*.o.#.                     (1 <= m, n <= 50) *:海洋,战船可以停在上面.      o:浮冰,战船 ...

  5. 『学了就忘』Linux基础 — 17、远程服务器关机及重启时的注意事项

    目录 1.为什么远程服务器不能关机 2.远程服务器重启时需要注意两点 3.不要在服务器访问高峰运行高负载命令 4.远程配置防火墙时不要把自己踢出服务器 5.指定合理的密码规范并定期更新 6.合理分配权 ...

  6. python 处理xml 数据

    1 import xml.sax 2 import xml.sax.handler 3 4 # python 处理xml 数据 类,将xml数据转化为字典 5 ''' 6 原数据:<?xml v ...

  7. Java测试开发--Maven用法(三)

    一.Maven简介 Maven 是java项目构建工具,统一包的管理,统一项目管理.项目编译,测试打包.部署. 二.Maven工程搭建: 1.新建maven工程,如下图 2. 新建工程后,jdk使用的 ...

  8. 关于 better-scroll 设置了以后无法滚动或不生效的问题

    首先在mounted里面注册组件  例:let scroll = new BScroll("#commondityLeftList")   然后打印实例化对象,例:console. ...

  9. 二.什么是Promise

    二.什么是Promise 1.理解 2.promise 的状态改变 3.promise的基本流程 4.promise的基本使用 1.理解 抽象表达: Promise 是JS 中进行异步编程的新的解决方 ...

  10. Visual Studio 2019连接MySQL数据库详细教程

    前言 如果要在 Visual Studio 2019中使用MySQL数据库,首先需要下载MySQL的驱动 Visual Studio默认只显示微软自己的SQL Server数据源,点击其它也是微软自己 ...