题面传送门

题意:

  • 有一个有向图 \(G\),其基图是一棵树
  • 求它拓扑序的个数 \(\bmod (10^9+7)\)
  • \(n \in [1,1000]\)

如果你按照拓扑排序的方法来做,那恐怕你已经想偏了。因为“求拓扑排序个数”本身就是一个 NP 问题,只能使用指数级的状压 \(dp\) 一类的算法来解决,而本题数据范围给到 \(1000\),暗示着我们要充分利用“\(G\) 的基图是一棵树”这个条件。

故可以想到树形 \(dp\)。\(dp[x][i]\) 表示将以 \(x\) 为根的子树进行拓扑排序后,\(x\) 位于拓扑序的第 \(i\) 位的方案数。

然后,然后……我就不会做了/kk

考虑转移,也就是要合并两个序列 \(x,y\),用 \(dp[x][i]\) 和 \(dp[y][j]\) 更新 \(newdp[x][k]\)。要分 \(x\) 拓扑序在 \(y\) 前面和在 \(y\) 后面两种情况。这里以 \(x\) 拓扑序在 \(y\) 前面为例。

一个非常蠢的想法是,枚举 \(x,y\) 在新序列中的位置,然后乘法原理转移。但是这样肯定爆炸。

注意到,当 \(i,j\) 确定后,\(k\) 可以取到的范围是一个区间。

因为原本在 \(x\) 前面的那 \(i-1\) 个数现在依旧在 \(x\) 前面;原本在 \(x\) 后面的数现在依旧在 \(x\) 后面。

而因为 \(x\) 的拓扑序小于 \(y\) 的拓扑序,原本在 \(y\) 后面的数现在也在 \(x\) 的后面;原本在 \(y\) 前面的那 \(j-1\) 个数,现在可以在 \(x\) 前面,也可以在 \(x\) 后面。

故我们有 \(k \in [i-1+1,i-1+j-1+1]=[i,i+j-1]\)。

\(k\) 的范围确定之后,转移当然还要使用组合数。我们考虑将原本 \(x\) 序列中的数在新序列中的位置确定下来,这样整个序列就已经确定了。

新序列中,比 \(x\) 小的那 \(k-1\) 个数中,必定有 \(i-1\) 个来自原序列,故这一部分的方案数为 \(\dbinom{k-1}{i-1}\)。

同理,填好比 \(x\) 大的数的方案数为 \(\dbinom{siz[x]+siz[y]-k}{siz[x]-i}\)

因此我们有状态转移方程:

\[newdp[x][k]+=dp[x][i] \times dp[y][j] \times \dbinom{k-1}{i-1} \times \dbinom{siz[x]+siz[y]-k}{siz[x]-i}
\]

这个转移是 \(\mathcal O(n^3)\),考虑对其进行优化。

首先给出朴素转移的伪代码:

for i from 1 to siz[x]:
for j from 1 to siz[y]:
for k from i to i+j-1:
transfer

注意到我们状态转移方程里 \(k\) 出现的频率很高,但是 \(j\) 出现的频率很低。

这启示我们,可以改变循环的顺序,先枚举 \(k\) 再枚举 \(j\),看看会发生些什么。

至于循环范围的推导……会做这道题的人应该不至于不会推导循环范围吧,实在不行打个表也行啊(

因此我们得到先枚举 \(k\) 再枚举 \(j\) 的伪代码:

for i from 1 to siz[x]:
for k from i to i+siz[y]-1:
for j from k-i+1 to siz[y]:
transfer

然后发现 \(j\) 变化的区间是一个后缀,故可以使用前缀和进行优化。

以上是 \(x\) 在 \(y\) 之前的情况。\(x\) 在 \(y\) 之后的情况也同理,只不过把 \(k\) 的变化范围改为 \([i+j,i+siz[y]]\) 而已,其余部分见代码。

/*
Contest: -
Problem: P4099
Author: tzc_wk
Time: 2020.8.14
*/
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define foreach(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define all(a) a.begin(),a.end()
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,0x3f,sizeof(a))
#define y1 y1010101010101
#define y0 y0101010101010
typedef pair<int,int> pii;
typedef long long ll;
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
const int MOD=1e9+7;
int n,ecnt,head[2005],to[2005],sgn[2005],nxt[2005];
ll dp[1005][1005],sum[1005][1005],C[1005][1005];
int siz[1005];
inline int get(char c){
return (c=='>')?1:-1;
}
inline void adde(int u,int v,int w){
to[++ecnt]=v;sgn[ecnt]=w;nxt[ecnt]=head[u];head[u]=ecnt;
}
inline void clear(){
ecnt=0;fill0(head);fill0(to);fill0(sgn);fill0(nxt);fill0(dp);
}
ll tmp[1005];
inline void dfs(int x,int fa){
siz[x]=1;dp[x][1]=1;
for(int i=head[x];i;i=nxt[i]){
int y=to[i],z=sgn[i];if(y==fa) continue;dfs(y,x);
fz(i,1,siz[x]) tmp[i]=dp[x][i],dp[x][i]=0;
if(z==1) fz(i,1,siz[x]) fz(k,i,i+siz[y]-1)
dp[x][k]=(dp[x][k]+tmp[i]*(sum[y][siz[y]]-sum[y][k-i]+MOD)%MOD*C[k-1][i-1]%MOD*C[siz[x]+siz[y]-k][siz[x]-i]%MOD)%MOD;
else fz(i,1,siz[x]) fz(k,i+1,i+siz[y])
dp[x][k]=(dp[x][k]+tmp[i]*sum[y][k-i]%MOD*C[k-1][i-1]%MOD*C[siz[x]+siz[y]-k][siz[x]-i]%MOD)%MOD;
siz[x]+=siz[y];
}
// fz(i,1,siz[x]){
// cout<<x<<" "<<i<<" "<<dp[x][i]<<endl;
// }
fz(i,1,siz[x]) sum[x][i]=(sum[x][i-1]+dp[x][i])%MOD;
}
inline void prework(){
fz(i,0,1000) C[i][0]=1;
fz(i,1,1000) fz(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
inline void solve(){
n=read();clear();
fz(i,1,n-1){
int u,v;char c;cin>>u>>c>>v;u++;v++;
adde(u,v,get(c));adde(v,u,-get(c));
}
dfs(1,0);
ll ans=0;
printf("%lld\n",sum[1][n]);
}
int main(){
prework();
int T=read();
while(T--) solve();
return 0;
}

洛谷 P4099 - [HEOI2013]SAO(树形 dp)的更多相关文章

  1. 洛谷 4099 [HEOI2013]SAO——树形DP

    题目:https://www.luogu.org/problemnew/show/P4099 结果还是看了题解才会…… 关键是状态,f[ i ][ j ] 表示 i 子树. i 号点是第 j 个出现的 ...

  2. 洛谷P4099 [HEOI2013]SAO(树形dp)

    传送门 HEOI的题好珂怕啊(各种意义上) 然后考虑树形dp,以大于为例 设$f[i][j]$表示$i$这个节点在子树中排名第$j$位时的总方案数(因为实际只与相对大小有关,与实际数值无关) 我们考虑 ...

  3. 洛谷$P4099\ [HEOI2013]\ SAO\ dp$

    正解:树形$dp$ 解题报告: 传送门$QwQ$. 考虑设$f_i$表示点$i$的子树内的拓扑序排列方案数有多少个. 发现这样不好合并儿子节点和父亲节点.于是加一维,设$f_{i,j}$表示点$i$的 ...

  4. [BZOJ3167][P4099][HEOI2013]SAO(树形DP)

    题目描述 Welcome to SAO ( Strange and Abnormal Online).这是一个 VR MMORPG, 含有 n 个关卡.但是,挑战不同关卡的顺序是一个很大的问题. 有 ...

  5. 3167: [Heoi2013]Sao [树形DP]

    3167: [Heoi2013]Sao 题意: n个点的"有向"树,求拓扑排序方案数 Welcome to Sword Art Online!!! 一开始想错了...没有考虑一个点 ...

  6. C++ 洛谷 2014 选课 from_树形DP

    洛谷 2014 选课 没学树形DP的,看一下. 首先要学会多叉树转二叉树. 树有很多种,二叉树是一种人人喜欢的数据结构,简单而且规则.但一般来说,树形动规的题目很少出现二叉树,因此将多叉树转成二叉树就 ...

  7. $loj10156/$洛谷$2016$ 战略游戏 树形$DP$

    洛谷loj Desription Bob 喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的方法.现在他有个问题. 现在他有座古城堡,古城堡的路形成一棵树.他要在这棵树的节点上放置最少数 ...

  8. [洛谷P2016] 战略游戏 (树形dp)

    战略游戏 题目描述 Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题. 他要建立一个古城堡,城堡中的路形成一棵树.他要在这棵树的结点上放置最少数目的士兵,使得 ...

  9. 洛谷P2607 [ZJOI2008]骑士(树形dp)

    题目描述 Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬. 最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战火绵延五百里, ...

随机推荐

  1. csp-j 复赛感想

    作者:博客园小蔡编程 这次是作者第一次参加csp-j的比赛 内心还是挺激动的 今天,作者就来和大家讨论一下这次csp-j的学习心得和感想 T1 分糖果 这题描述看似复杂 其实就是一道求最大取模的题 L ...

  2. Scrum Meeting 0602

    零.说明 日期:2021-6-2 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 困难 qsy PM&前端 完成后端管理 ...

  3. oo第三单元学习总结

    OO第三单元小结 一.JML语言理论基础及工具链梳理 在本单元我们学习了JML语言的一些基础知识,能够让我们看懂简单的JML规格并写出对应代码, 主要用到的知识点有:   1.requires 该子句 ...

  4. 运维常用python库&模块

    sutil:是一个跨平台库(https://github.com/giampaolo/psutil)能够实现获取系统运行的进程和系统利用率(内存,CPU,磁盘,网络等),主要用于系统监控,分析和系统资 ...

  5. logstash收集的日志输出到elasticsearch中

    logstash收集的日志输出到elasticsearch中 一.需求 二.实现步骤 1.编写pipeline文件 1.`elasticsearch`配置参数解析: 2.可能会报的一个异常 2.准备测 ...

  6. Noip模拟47 2021.8.25

    期望得分:55+24+53 实际得分:0+0+3 乐死 累加变量清零了吗? 打出更高的部分分暴力删了吗? 样例解释换行你看见了吗? T1 Prime 打出55分做法没删原来的暴力,结果就轻松挂55分 ...

  7. 矩阵中的路径 牛客网 剑指Offer

    矩阵中的路径 牛客网 剑指Offer 题目描述 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下 ...

  8. python环境搭建、pycharm安装

    一.      实验目标 (1)  Python环境搭建 (2)  会pycharm安装和使用 (3)  了解python程序设计流程 二.      实验内容 1.勾选Add Python 3.7 ...

  9. Dataworks批量刷数优化方案探讨

    Dataworks批量刷数优化方案探讨 在数据仓库的日常使用中,经常会有批量补数据,或者逻辑调整后批量重跑数据的场景. 批量刷数的实现方式,因调度工具差异而各有不同. Dataworks调度批量刷数局 ...

  10. SpringBoot 居然有 44 种应用启动器

    啥是应用启动器?SpringBoot集成了spring的很多模块,比如tomcat.redis等等.你用SpringBoot搭建项目,只需要在pom.xml引入相关的依赖,和在配置文件中简单的配置就可 ...