P4099 [HEOI2013]SAO

我们设$f[u][k]$表示以拓扑序编号为$k$的点$u$,以$u$为根的子树中的元素所组成的序列方案数

蓝后我们在找一个以$v$为根的子树。

我们的任务就是在合并这两棵树时维护$f[u][k]$

合并时,$v$的元素可能全在点$u$的前/后面,也可能都有。

分类讨论:

1.当有$p(p\in [0,siz[v]])$个元素插入到点$u$(拓扑序)前面时

我们知道插入后点$u$的拓扑序为$k$

那么插入前的拓扑序即为$k-p$

∴插入前子树$u$对应的状态就是$f[u][k-p]$

设$i=k-p$

那么插入的方案数就等价于在$k-1$位置($u$是固定最后的)中选择$i-1$个位置$*f[u][i]$

$=C(k-1,i-1)*f[u][i]$

其中$i\in [1,min(siz[u],k)]$

2.当有$p(p\in [0,siz[v]])$个元素插入到点$u$(拓扑序)后面时

可以这样表示$p=siz[v]-(k-i)=siz[v]-k+i$

而原来$u$(拓扑序)后面的元素共有$siz[u]-i$个

∴方案数$=C(siz[u]-i+p,p)*f[v][j]=C(siz[u]+siz[v]-k,siz[u]-i)*f[v][j]$

$j$的范围需要分类:

  1. $u<v$(拓扑序)$j\in[k-i+1,siz[v]]$,即点$v$不能填充到拓扑序$<u$的地方
  2. $u>v$(拓扑序)$j\in[1,k-i]$

整理一下:

$f[u][k]=\sum_{i=1}^{min(siz[u],k)}\sum_{j}(分类讨论)*C(k-1,i-1)*C(siz[u]+siz[v]-k,siz[u]-i)$

然鹅这是$O(n^{3})$

发现是$j$是连续一段区间,可以前缀和处理

蓝后就愉快地变成$O(n^{2})$了

end.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#define re register
using namespace std;
typedef long long ll;
void read(int &x){
char c=getchar(); x=;
while(!isdigit(c)) c=getchar();
while(isdigit(c)) x=(x<<)+(x<<)+(c^),c=getchar();
}int wt[];
void output(ll x){
if(!x) {putchar(); return;}
int l=;
while(x) wt[++l]=x%,x/=;
while(l) putchar(wt[l--]+);
}
int min(int &a,int &b) {return a<b?a:b;}
const int p=1e9+;
ll mod(ll a){return a<p?a:a-p;}
#define N 1001
int t,n,siz[N];
ll f[N][N],C[N][N],sum[N][N],ans;
int cnt,hd[N],nxt[N<<],ed[N],poi[N<<],dir[N<<];
bool vis[N];
void adde(int x,int y,int v){
nxt[ed[x]]=++cnt; hd[x]=hd[x]? hd[x]:cnt;
ed[x]=cnt; poi[cnt]=y; dir[cnt]=v;
}
void clears(){//清空数据
for(re int i=;i<=n;++i){
f[i][]=; sum[i][]=;
vis[i]=; siz[i]=;
hd[i]=ed[i]=;
nxt[i]=nxt[i+n]=;
for(re int j=;j<=n;++j) f[i][j]=sum[i][j]=;
}cnt=ans=;
}
void dfs(int u){
vis[u]=;
for(int z=hd[u];z;z=nxt[z]){
int v=poi[z];
if(vis[v]) continue;
dfs(v);
if(dir[z]){//u<v(拓扑序)
for(int k=siz[u]+siz[v];k>=;--k){
ll tmp=;
for(int i=min(siz[u],k);i>=;--i){
int l=k-i+,r=siz[v]; //下面只要修改l,r即可
if(l>r) continue;
ll r1=mod(sum[v][r]-sum[v][l-]+p);
ll r2=C[k-][i-]*C[siz[u]+siz[v]-k][siz[u]-i]%p;
tmp=mod(tmp+f[u][i]*r1%p*r2%p);
}f[u][k]=tmp;
}
}else{//u>v(拓扑序)
for(int k=siz[u]+siz[v];k>=;--k){
ll tmp=;
for(int i=min(siz[u],k);i>=;--i){
int l=,r=k-i;
if(l>r) continue;
ll r1=mod(sum[v][r]-sum[v][l-]+p);
ll r2=C[k-][i-]*C[siz[u]+siz[v]-k][siz[u]-i]%p;
tmp=mod(tmp+f[u][i]*r1%p*r2%p);
}f[u][k]=tmp;
}
}siz[u]+=siz[v];
}
for(int i=;i<=siz[u];++i)//维护前缀和
sum[u][i]=mod(sum[u][i-]+f[u][i]);
}
int main(){
read(t); char opt[]; int q1,q2,q3;
for(re int i=;i<N;++i)//组合数预处理
for(re int j=;j<=i;++j)
C[i][j]=(!j||j==i)?:mod(C[i-][j]+C[i-][j-]);
while(t--){
read(n); clears();
for(re int i=;i<n;++i){
read(q1); scanf("%s",opt); read(q2);
q3=(opt[]=='<'); ++q1; ++q2;
adde(q1,q2,q3); adde(q2,q1,q3^);
}dfs();
for(re int i=;i<=n;++i) ans=mod(ans+f[][i]);
output(ans); putchar('\n');
}return ;
}

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

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

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

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

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

  3. BZOJ 3167 [Heoi2013]Sao ——树形DP

    BZOJ4824的强化版. 改变枚举的方案,使用前缀和进行DP优化. 然后复杂度就是$O(n^2)$了. #include <map> #include <cmath> #in ...

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

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

  5. P4099 [HEOI2013]SAO

    P4099 [HEOI2013]SAO 贼板子有意思的一个题---我()竟然没看题解 有一张连成树的有向图,球拓扑序数量. 树形dp,设\(f[i][j]\)表示\(i\)在子树中\(i\)拓扑序上排 ...

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

    题面传送门 题意: 有一个有向图 \(G\),其基图是一棵树 求它拓扑序的个数 \(\bmod (10^9+7)\) \(n \in [1,1000]\) 如果你按照拓扑排序的方法来做,那恐怕你已经想 ...

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

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

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

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

  9. [HEOI2013]SAO(树上dp,计数)

    [HEOI2013]SAO (这写了一个晚上QAQ,可能是我太蠢了吧.) 题目说只有\(n-1\)条边,然而每个点又相互联系.说明它的结构是一个类似树的结构,但是是有向边连接的,题目问的是方案个数,那 ...

随机推荐

  1. canvas二:绘制圆和其他曲线

    1.绘制圆 绘制圆是canvas里面不可缺少的功课,而且绘制圆在canvas中的用处很多,好嘞,开扯 绘制圆需要用到arc这个方法: arc(X坐标,Y坐标,半径,起始弧度,结束弧度,旋转方向): 弧 ...

  2. Chisel常用命令总结

    Chisel简介 Chisel是Facebook开源的一款lldb调试工具,其实就是对系统lldb命令的封装,开发者可以通过简化的命令更方便的进行调试工作.开源地址:https://github.co ...

  3. HTTP/2笔记之错误处理和安全

    零.前言 这里整理了一下错误和安全相关部分简单记录. 一.HTTP/2错误 1. 错误定义 HTTP/2定义了两种类型错误: 导致整个连接不可使用的错误为连接错误(connection error) ...

  4. 使用CDN的网络访问过程

    CDN是指内容分发网络,在网络各处架设节点服务器,当用户访问时,CDN系统会根据网络流量.到用户的距离等因素将请求导向离用户最近的节点上. 访问过程是: 1.用户向浏览器提供要访问的域名. 2.浏览器 ...

  5. Linux Netfilter注册钩子点

    注册钩子点首先要包含响应的头文件,因为这应该已经属于对kernel的编程了. #include <linux/module.h> #include <linux/kernel.h&g ...

  6. shell中特殊变量$0 $1 $# $$ $! $?的涵义

    $0: 执行脚本的名字 $*和$@: 将所有参数返回 $#: 参数的个数 $_: 代表上一个命令的最后一个参数 $$: 代表所在命令的PID $!: 代表最后执行的后台命令的PID $?: 代表上一个 ...

  7. Express框架(http服务器 + 路由)

    index.js 使用express框架搭建http服务器,和实现路由功能. var express = require('express'); var app = express(); // 主页输 ...

  8. [SQL] 常用查询脚本

    查询哪些存储过程使用了某个表 select b.name from syscomments a,sysobjects b where a.id=b.id and a.text LIKE '%ftblo ...

  9. Linux系统下Redis缓存安装配置

    Redis是一个高性能的key-value数据库,现时越来越多企业与应用使用Redis作为缓存服务器.楼主是一枚JAVA后端程序员,也算是半个运维工程师了.在Linux服务器上搭建Redis,怎么可以 ...

  10. LightBGM之Dataset

    最近使用了LightBGM的Dataset,记录一下: 1.说明: classlightgbm.Dataset(data, label=None, reference=None, weight=Non ...