P4099 [HEOI2013]SAO(树形dp)
我们设$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$的范围需要分类:
- $u<v$(拓扑序)$j\in[k-i+1,siz[v]]$,即点$v$不能填充到拓扑序$<u$的地方
- $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)的更多相关文章
- [BZOJ3167][P4099][HEOI2013]SAO(树形DP)
题目描述 Welcome to SAO ( Strange and Abnormal Online).这是一个 VR MMORPG, 含有 n 个关卡.但是,挑战不同关卡的顺序是一个很大的问题. 有 ...
- 3167: [Heoi2013]Sao [树形DP]
3167: [Heoi2013]Sao 题意: n个点的"有向"树,求拓扑排序方案数 Welcome to Sword Art Online!!! 一开始想错了...没有考虑一个点 ...
- BZOJ 3167 [Heoi2013]Sao ——树形DP
BZOJ4824的强化版. 改变枚举的方案,使用前缀和进行DP优化. 然后复杂度就是$O(n^2)$了. #include <map> #include <cmath> #in ...
- 洛谷 4099 [HEOI2013]SAO——树形DP
题目:https://www.luogu.org/problemnew/show/P4099 结果还是看了题解才会…… 关键是状态,f[ i ][ j ] 表示 i 子树. i 号点是第 j 个出现的 ...
- P4099 [HEOI2013]SAO
P4099 [HEOI2013]SAO 贼板子有意思的一个题---我()竟然没看题解 有一张连成树的有向图,球拓扑序数量. 树形dp,设\(f[i][j]\)表示\(i\)在子树中\(i\)拓扑序上排 ...
- 洛谷 P4099 - [HEOI2013]SAO(树形 dp)
题面传送门 题意: 有一个有向图 \(G\),其基图是一棵树 求它拓扑序的个数 \(\bmod (10^9+7)\) \(n \in [1,1000]\) 如果你按照拓扑排序的方法来做,那恐怕你已经想 ...
- 洛谷P4099 [HEOI2013]SAO(树形dp)
传送门 HEOI的题好珂怕啊(各种意义上) 然后考虑树形dp,以大于为例 设$f[i][j]$表示$i$这个节点在子树中排名第$j$位时的总方案数(因为实际只与相对大小有关,与实际数值无关) 我们考虑 ...
- 洛谷$P4099\ [HEOI2013]\ SAO\ dp$
正解:树形$dp$ 解题报告: 传送门$QwQ$. 考虑设$f_i$表示点$i$的子树内的拓扑序排列方案数有多少个. 发现这样不好合并儿子节点和父亲节点.于是加一维,设$f_{i,j}$表示点$i$的 ...
- [HEOI2013]SAO(树上dp,计数)
[HEOI2013]SAO (这写了一个晚上QAQ,可能是我太蠢了吧.) 题目说只有\(n-1\)条边,然而每个点又相互联系.说明它的结构是一个类似树的结构,但是是有向边连接的,题目问的是方案个数,那 ...
随机推荐
- canvas二:绘制圆和其他曲线
1.绘制圆 绘制圆是canvas里面不可缺少的功课,而且绘制圆在canvas中的用处很多,好嘞,开扯 绘制圆需要用到arc这个方法: arc(X坐标,Y坐标,半径,起始弧度,结束弧度,旋转方向): 弧 ...
- Chisel常用命令总结
Chisel简介 Chisel是Facebook开源的一款lldb调试工具,其实就是对系统lldb命令的封装,开发者可以通过简化的命令更方便的进行调试工作.开源地址:https://github.co ...
- HTTP/2笔记之错误处理和安全
零.前言 这里整理了一下错误和安全相关部分简单记录. 一.HTTP/2错误 1. 错误定义 HTTP/2定义了两种类型错误: 导致整个连接不可使用的错误为连接错误(connection error) ...
- 使用CDN的网络访问过程
CDN是指内容分发网络,在网络各处架设节点服务器,当用户访问时,CDN系统会根据网络流量.到用户的距离等因素将请求导向离用户最近的节点上. 访问过程是: 1.用户向浏览器提供要访问的域名. 2.浏览器 ...
- Linux Netfilter注册钩子点
注册钩子点首先要包含响应的头文件,因为这应该已经属于对kernel的编程了. #include <linux/module.h> #include <linux/kernel.h&g ...
- shell中特殊变量$0 $1 $# $$ $! $?的涵义
$0: 执行脚本的名字 $*和$@: 将所有参数返回 $#: 参数的个数 $_: 代表上一个命令的最后一个参数 $$: 代表所在命令的PID $!: 代表最后执行的后台命令的PID $?: 代表上一个 ...
- Express框架(http服务器 + 路由)
index.js 使用express框架搭建http服务器,和实现路由功能. var express = require('express'); var app = express(); // 主页输 ...
- [SQL] 常用查询脚本
查询哪些存储过程使用了某个表 select b.name from syscomments a,sysobjects b where a.id=b.id and a.text LIKE '%ftblo ...
- Linux系统下Redis缓存安装配置
Redis是一个高性能的key-value数据库,现时越来越多企业与应用使用Redis作为缓存服务器.楼主是一枚JAVA后端程序员,也算是半个运维工程师了.在Linux服务器上搭建Redis,怎么可以 ...
- LightBGM之Dataset
最近使用了LightBGM的Dataset,记录一下: 1.说明: classlightgbm.Dataset(data, label=None, reference=None, weight=Non ...