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 结果就是一个 $\prod_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] 。
于是只要把每一个节点的贡献乘起来就好了。
代码
#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;
}
UOJ#290. 【ZJOI2017】仙人掌 仙人掌,Tarjan,计数,动态规划,树形dp,递推的更多相关文章
- 【BZOJ4784】[ZJOI2017]仙人掌(Tarjan,动态规划)
[BZOJ4784][ZJOI2017]仙人掌(Tarjan,动态规划) 题面 BZOJ 洛谷 题解 显然如果原图不是仙人掌就无解. 如果原图是仙人掌,显然就是把环上的边给去掉,变成若干森林连边成为仙 ...
- 动态规划——树形dp
动态规划作为一种求解最优方案的思想,和递归.二分.贪心等基础的思想一样,其实都融入到了很多数论.图论.数据结构等具体的算法当中,那么这篇文章,我们就讨论将图论中的树结构和动态规划的结合——树形dp. ...
- 洛谷P2515 [HAOI2010]软件安装(tarjan缩点+树形dp)
传送门 我们可以把每一个$d$看做它的父亲,这样这个东西就构成了一个树形结构 问题是他有可能形成环,所以我们还需要一遍tarjan缩点 缩完点后从0向所有入度为零的点连边 然后再跑一下树形dp就行了 ...
- BZOJ 2427 /HAOI 2010 软件安装 tarjan缩点+树形DP
终于是道中文题了.... 当时考试的时候就考的这道题.... 果断GG. 思路: 因为有可能存在依赖环,所以呢 先要tarjan一遍 来缩点. 随后就进行一遍树形DP就好了.. x表示当前的节点.j表 ...
- bzoj 2427 [HAOI2010]软件安装 Tarjan缩点+树形dp
[HAOI2010]软件安装 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 2029 Solved: 811[Submit][Status][Dis ...
- BZOJ_2111_[ZJOI2010]Perm 排列计数_树形DP+组合数学
Description 称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic ...
- bzoj 2111: [ZJOI2010]Perm 排列计数【树形dp+lucas】
是我想复杂了 首先发现大于关系构成了一棵二叉树的结构,于是树形dp 设f[i]为i点的方案数,si[i]为i点的子树大小,递推式是\( f[i]=f[i*2]*f[i*2+1]*C_{si[i]-1} ...
- [SHOI2008]仙人掌图 II——树形dp与环形处理
题意: 给定一个仙人掌,边权为1 距离定义为两个点之间的最短路径 直径定义为距离最远的两个点的距离 求仙人掌直径 题解: 类比树形dp求直径. f[i]表示i向下最多多长 处理链的话,直接dp即可. ...
- uoj#290. 【ZJOI2017】仙人掌(数数+仙人掌+树形dp)
传送门 这图可以说是非常形象了2333 模拟赛的时候打了个表发现为一条链的时候答案是\(2^{n-2}\)竟然顺便过了第一个点 然后之后订正的时候强联通分量打错了调了一个上午 首先不难发现我们可以去掉 ...
随机推荐
- vue路由实现多视图的单页应用
多视图的单页应用:在一个页面中实现多个页面不同切换,url也发生相应变化. router-view结合this.$router.push("/pickUp")实现,效果如下: 当点 ...
- I2C(二) linux2.6
目录 I2C(二) linux2.6 总线驱动 关键结构 入口 i2c_add_adapter 硬件操作 设备驱动 入口 注册 attach_adapter eeprom_detect i2c_att ...
- 一文了解Python的线程
问题 什么是线程? 如何创建.执行线程? 如何使用线程池ThreadPoolExecutor? 如何避免资源竞争问题? 如何使用Python中线程模块threading提供的常用工具? 目录 1. 什 ...
- C语言 内存管理(转)
转自 https://blog.csdn.net/u011616739/article/details/61621815 C语言 内存管理 1.内存分区 C源代码进过预处理.编译.汇编和链接4步生成 ...
- 树莓派的系统安装,并且利用网线直连 Mac 进行配置
最近单位给了我一个新的树莓派3B+让我自己玩.下面是我记录的我如何安装 Raspbian Stretch Lite 系统,然后如何成功不用独立显示屏而利用 MacBook 对其进行配置. 安装 Ras ...
- knockoutjs复杂对象的可观察性
问题 对于一般数据结构: 1. 对于基本类型的数据的变更的可观察性(observable), 可以使用 ko.observable(xxx) 来声明一个 observable对象, 或将其绑定到视图 ...
- (Python3) 求中位数 代码
def zhongweishu(a): new=sorted(a) if len(a)%2==0: s=(new[int(len(a)/2-1)]+new[int(len(a)/2)])/2 else ...
- java中import详解
前言 import与package机制相关,这里先从package入手,再讲述import以及static import的作用. package package名称就像是我们的姓,而class名称就像 ...
- 初识正则表达式matcher.group
matcher.group中group是匹配()的,group(0)指的是整个串,group(1) 指的是第一个括号里的内容,group(2)指的第二个括号里的内容,以此类推. 例如: str = & ...
- idea编译错误提示编译版本不对,需要注意的配置