UOJ#290. 【ZJOI2017】仙人掌 仙人掌,Tarjan,计数,动态规划,树形dp,递推
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ290.html
题解
真是一道好题!
首先,如果不是仙人掌直接输出 0 。
否则,显然先把环上的边删光。
问题转化成多个树求解,把答案乘起来即可。
现在我们考虑如何求一个树的答案。
再转化一下题意可以变成选出若干条长度至少为 2 的路径使得它们两两没有交。
标算十分优美。放到后面讲。
我先讲讲我的sb做法。
我们先来看看暴力 dp 怎么做:
设 dp[x][i] 表示子树 x ,在 x 节点上还有 i 个路径的端点待合并。
那么,加入 x 的一个子树 y 的贡献时,我们可以得到转移方程:
设 v = dp[y][0] + dp[y][1]
dp'[x][i] = v * (dp[x][i]+dp[x][i-1]+(i+1)dp[x][i+1])
我们来分几种情况讨论一下这个式子的正确性:
1. y 这个节点上没有路径待合并。
那么,首先可以选择不对 x 的待合并路径数做贡献,也就是 dp[y][0] * dp[x][i]
然后,可以加入一条 y 到 x 的长度为 1 的路径,必须要合并(因为长度至少得是 2 ),所以它可以有两种状态:一种是加入到待合并集合中,一种是在待合并集合中找一条路径合并。 所以分别是 dp[y][0] * dp[x][i-1] 和 dp[y][0] * (i+1)dp[x][i+1]
2. y 这个节点上有一条路径待合并。
我们考虑把这条路径往 x 延长 1 ,那么他就可以结束它的待合并状态: dp[y][1] * dp[x][i]; 或者继续保持待合并状态或者和待合并集合中的路径合并: dp[y][1] * dp[x][i-1] 和 dp[y][1] * (i+1)dp[x][i+1]
所以就是上面那个式子啦!
所以,我们可以化简一下式子,发现 x 的 dp 结果就是一个 ∏y(dp[y][0]+dp[y][1]) 乘一个 关于 x 的儿子个数的固定函数。我们只要算 dp[x][0] 和 dp[x][1] 所以我们只要两个这样的函数就好了。
我们来看看怎么算这个固定函数。
f[x+1][i]=f[x][i]+f[x][i−1]+(i+1)f[x][i+1]
我们只要求 f[i][0] 和 f[i][1] 。
打个表
OEIS
搜到了!
抄上去
过了!
好了我们来看看怎么正经地算这个东西。
我们考虑 f[i][0] 和 f[i][1] 的组合意义。
f[i][0] 就是 i 个带标号点,我们将他们选择若干组来配对的方案数。
f[i][1] 就是 i 个带标号点,我们将他们选择若干组来匹配,并留出一个特殊点。
那么可以得到:
f[i][0] = f[i-1][0] + f[i-1][1]
f[i][1] = f[i-1][0] + f[i-1][1] + f[i-2][1] * (i-1)
解释一下 f[i][1] 的转移原因:
第 i 个点作为特殊点 : f[i][1] +=f[i-1][0]
第 i 个点不配对: f[i][1] += f[i-1][1]
第 i 个点在前面找一个点配对: f[i][1] += f[i-2][1] * (i-1)
讲完了。
你现在可以在 O(n) 的时间复杂度内解决这个问题。
接下来讲优美的标算。
由于不能有长度为 1 的路径,而且会有空出来的位置。
我们可以把空出来的位置当作长度为 1 的路径,那么就是求任何路径长度至少为 1 的路径集合 覆盖所有边的方案数。
先加上每条边上都覆盖了长度为 1 的路径。
对于每一个点,设连接这一个点的路径集合为 S ,那么就是相当于给 S 中的路径端点选择若干组来配对,求方案数。显然这就是之前的求过的 f[|S|][0] 。
于是只要把每一个节点的贡献乘起来就好了。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | #include <bits/stdc++.h> #define clr(x) memset(x,0,sizeof (x)) #define For(i,a,b) for (int i=a;i<=b;i++) #define Fod(i,b,a) for (int i=b;i>=a;i--) #define pb push_back #define mp make_pair #define fi first #define se second #define _SEED_ ('C'+'L'+'Y'+'A'+'K'+'I'+'O'+'I') #define outval(x) printf(#x" = %d\n",x) #define outvec(x) printf("vec "#x" = ");for (auto _v : x)printf("%d ",_v);puts("") #define outtag(x) puts("----------"#x"----------") using namespace std; typedef long long LL; LL read(){ LL x=0,f=0; char ch= getchar (); while (! isdigit (ch)) f|=ch== '-' ,ch= getchar (); while ( isdigit (ch)) x=(x<<1)+(x<<3)+(ch^48),ch= getchar (); return f?-x:x; } const int N=500005,M=2e6+5,mod=998244353; void Add( int &x, int y){ if ((x+=y)>=mod) x-=mod; } int T,n,m; vector < int > e[N]; struct Graph{ int cnt,y[M],nxt[M],fst[N]; void clear( int n){ For(i,0,n) fst[i]=0; cnt=1; } void add( int a, int b){ y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt; } }g; int dfn[N],low[N],st[N],Time,top; int visE[M],vis[N]; int tag[N]; int circle_cnt; int a0[N],a1[N]; void Tarjan( int x){ dfn[x]=low[x]=++Time; st[++top]=x; for ( int i=g.fst[x];i;i=g.nxt[i]){ if (visE[i>>1]) continue ; visE[i>>1]=1; int y=g.y[i]; if (!dfn[y]){ Tarjan(y); low[x]=min(low[x],low[y]); if (low[y]==dfn[x]){ circle_cnt++; while (st[top]!=x) top--; } else if (low[y]>dfn[x]){ e[x].pb(y),e[y].pb(x); while (st[top]!=x) top--; } } else { low[x]=min(low[x],dfn[y]); } } } int f[N][2]; void dfs( int x, int pre){ vis[x]=1; int tmp=1,cnt=0; for ( auto y : e[x]) if (y!=pre){ dfs(y,x); tmp=(LL)tmp*(f[y][0]+f[y][1])%mod; cnt++; } f[x][0]=(LL)a0[cnt]*tmp%mod; f[x][1]=(LL)a1[cnt]*tmp%mod; } int main(){ a1[1]=1,a1[2]=2; a0[0]=1,a0[1]=1,a0[2]=2; For(i,3,N-1){ a1[i]=((LL)a1[i-2]*(i-1)+a1[i-1]+a0[i-1])%mod; a0[i]=(a0[i-1]+a1[i-1])%mod; } T=read(); while (T--){ n=read(),m=read(); For(i,0,n) e[i].clear(),vis[i]=0; g.clear(n); For(i,1,m){ int x=read(),y=read(); g.add(x,y); g.add(y,x); } Time=top=circle_cnt=0; For(i,1,n) dfn[i]=low[i]=st[i]=tag[i]=0; For(i,0,m+2) visE[i]=0; Tarjan(1); if (circle_cnt+n-1!=m){ puts ( "0" ); continue ; } int ans=1; For(i,1,n) if (!vis[i]){ dfs(i,0); ans=(LL)ans*f[i][0]%mod; } printf ( "%d\n" ,ans); } return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!