Loading web-font TeX/Size1/Regular


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][i1]+(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;
}

  

posted @   zzd233  阅读(226)  评论(0编辑  收藏  举报
编辑推荐:
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
阅读排行:
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!

点击右上角即可分享
微信分享提示