[BZOJ 3167][HEOI 2013]SAO
[BZOJ 3167][HEOI 2013]SAO
题意
对一个长度为 \(n\) 的排列作出 \(n-1\) 种限制, 每种限制形如 "\(x\) 在 \(y\) 之前" 或 "\(x\) 在 \(y\) 之后". 且保证任意两点之间都有直接或间接的限制关系. 求方案数量.
\(n\le 1000\).
题解
Sword Art Online还行
拖了很久终于想起这题了...
首先我们发现这个限制关系是树状的, 那么我们尝试用子树来定义状态. 设 \(dp_{i,j}\) 表示以 \(i\) 为根的子树中的元素组成的合法排列且 \(i\) 在升序下排在第 \(j\) 名的方案数量. 那么我们要做的就是将两个排列在符合限制的条件下保序合并.
保序合并比较简单, 枚举一个排列在合并后的排列中占据哪些位置就可以了. 显然这是一个简单的组合数.
为了符合限制条件, 我们需要把在最终排列中排在当前根之前的结点和之后的结点区别对待. 枚举子树中有 \(j\) 个点排在根之前. 然后将限制分两类讨论:
若限制是子结点先于根节点, 那么设根节点为 \(r\), 子结点为 \(s\), 有:
\[dp_{r,i+j}={i+j-1\choose i-1}{\text{size}_r+\text{size}_s-i-j\choose \text{size}_s-j}dp_{r,i}\sum_{k=1}^jdp_{s,k}
\]若限制是根结点先于子结点, 那么有:
\[dp_{r,i+j}={i+j-1\choose i-1}{\text{size}_r+\text{size}_s-i-j\choose \text{size}_s-j}dp_{r,i}\sum_{k=j+1}^{\text{size}_s}dp_{s,k}
\]
其中等式右侧(包括 \(\text{size}_r\) )都是合并 \(s\) 所在子树信息前的信息.
不难发现这个东西是个 \(O(n^3)\) 的爆炸复杂度. 但是转移式最后的和式是个前/后缀和的形式, 预处理一下就可以均摊 \(O(1)\) 得到那个和式的值了.
于是复杂度就变成 \(O(n^2)\) 了.
以及这题复杂度的分析, 我们直接看转移式会感觉它单次转移是 \(O(n^2)\) 的, 实际上 \(i\) 和 \(j\) 的枚举范围分别只有 \(\text{size}_r\) 和 \(\text{size}_s\). 总枚举量就是 "当前要合并的子树大小" 与 "已经合并过的兄弟子树大小之和" 的积. 那么对于 \(r\) 的转移枚举量其实就等于以 \(r\) 为LCA的点对数量. 那么整棵树的总枚举量就是 \(O(n^2)\) 了.
参考代码
然而排列是 \([0,n)\) 的...
#include <bits/stdc++.h>
const int MAXN=1010;
const int MOD=1e9+7;
struct Edge{
int from;
int to;
int typ;
Edge* next;
};
Edge E[MAXN*2];
Edge* head[MAXN];
Edge* top=E;
int n;
int tmp[MAXN];
int inv[MAXN];
int fact[MAXN];
int size[MAXN];
int pf[MAXN][MAXN];
int sf[MAXN][MAXN];
int dp[MAXN][MAXN];
int C(int,int);
void DFS(int,int);
int Pow(int,int,int);
void Insert(int,int,int);
int main(){
int T;
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
memset(pf,0,sizeof(pf));
memset(sf,0,sizeof(sf));
memset(head,0,sizeof(head));
top=E;
scanf("%d",&n);
fact[0]=1;
for(int i=1;i<=n;i++)
fact[i]=1ll*fact[i-1]*i%MOD;
inv[n]=Pow(fact[n],MOD-2,MOD);
for(int i=n;i>=1;i--)
inv[i-1]=1ll*inv[i]*i%MOD;
for(int i=1;i<n;i++){
int a,b;
char op;
scanf("%d %c%d",&a,&op,&b);
Insert(a,b,op=='>');
Insert(b,a,op=='<');
}
DFS(0,-1);
printf("%d\n",sf[0][1]);
}
return 0;
}
void DFS(int root,int prt){
size[root]=1;
dp[root][1]=1;
for(Edge* k=head[root];k!=NULL;k=k->next){
if(k->to!=prt){
DFS(k->to,root);
memset(tmp,0,sizeof(tmp));
if(k->typ){
for(int i=1;i<=size[root];i++){
for(int j=1;j<=size[k->to];j++){
tmp[i+j]=(tmp[i+j]+1ll*C(i+j-1,i-1)*C(size[root]+size[k->to]-i-j,size[k->to]-j)%MOD*dp[root][i]%MOD*pf[k->to][j])%MOD;
}
}
}
else{
for(int i=1;i<=size[root];i++){
for(int j=0;j<size[k->to];j++){
tmp[i+j]=(tmp[i+j]+1ll*C(i+j-1,i-1)*C(size[root]+size[k->to]-i-j,size[k->to]-j)%MOD*dp[root][i]%MOD*sf[k->to][j+1])%MOD;
}
}
}
size[root]+=size[k->to];
memcpy(dp[root],tmp,sizeof(tmp));
}
}
for(int i=1;i<=size[root];i++)
pf[root][i]=(dp[root][i]+pf[root][i-1])%MOD;
for(int i=size[root];i>=1;i--)
sf[root][i]=(dp[root][i]+sf[root][i+1])%MOD;
}
inline void Insert(int from,int to,int typ){
top->from=from;
top->to=to;
top->typ=typ;
top->next=head[from];
head[from]=top++;
}
inline int C(int n,int m){
return n<0||m<0||n<m?0:1ll*fact[n]*inv[m]%MOD*inv[n-m]%MOD;
}
inline int Pow(int a,int n,int p){
int ans=1;
while(n>0){
if(n&1)
ans=1ll*a*ans%p;
a=1ll*a*a%p;
n>>=1;
}
return ans;
}

[BZOJ 3167][HEOI 2013]SAO的更多相关文章
- 解题:HEOI 2013 SAO
题面 不好讲,直接上式子吧=.= 设$dp[i][j]$表示考虑完$i$的子树后$i$的排名为$j$的方案数,然后转移类似树形背包,具体来说是(这里假设子树在$i$后选,其实反过来还用这个式子答案也是 ...
- [HEOI 2013]SAO
Description 题库连接 给你一个 \(n\) 个节点的有向树,问你这棵树的拓扑序个数,对大质数取模.多测,测试组数 \(T\). \(1\leq n\leq 1000, 1\leq T\le ...
- [BZOJ 3123] [SDOI 2013]森林(可持久化线段树+并查集+启发式合并)
[BZOJ 3123] [SDOI 2013]森林(可持久化线段树+启发式合并) 题面 给出一个n个节点m条边的森林,每个节点都有一个权值.有两种操作: Q x y k查询点x到点y路径上所有的权值中 ...
- [BZOJ 3173] [TJOI 2013] 最长上升子序列(fhq treap)
[BZOJ 3173] [TJOI 2013] 最长上升子序列(fhq treap) 题面 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.每插入一个数 ...
- BZOJ 3236 AHOI 2013 作业 莫队+树状数组
BZOJ 3236 AHOI 2013 作业 内存限制:512 MiB 时间限制:10000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目大意: 此时己是凌晨两点,刚刚做了Co ...
- [BZOJ 3144][HNOI 2013] 切糕
题目大意 切糕是 (p times q times r) 的长方体,每个点有一个违和感 (v_{x, y, z}).先要水平切开切糕(即对于每个纵轴,切面与其有且只有一个交点),要求水平上相邻两点的切 ...
- BZOJ 3167: [Heoi2013]Sao
3167: [Heoi2013]Sao Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 96 Solved: 36[Submit][Status][D ...
- 【BZOJ 3165】【HEOI 2013】Segment
往区间上覆盖一次函数,做法是用线段树维护标记永久化. 每次都忘了线段树要4倍空间,第一次交总是RE,再这么手残的话考场上就真的要犯逗了. #include<cstdio> #include ...
- [HEOI 2013 day2] SAO (树形动态规划)
题目大意 给一棵N个节点的有向树(N <= 1000),求其拓扑序列个数. 思路 我们将任意一个点作为根,用dp[i][j]表示以节点i为根的子树满足节点i在第j个位置上的拓扑序列的个数.在求节 ...
随机推荐
- 求XF+闭包(第十一届河南省省赛真题)
题目描述 如何设计一个好的数据库不仅仅是一个理论研究问题,也是一个实际应用问题.在关系数据库中不满足规范化理论的数据库设计会存在冗余.插入异常.删除异常等现象. 设R(U)是一个关系模式,U={ A1 ...
- UVA 1605 Building for UN(思维)
题目链接: https://cn.vjudge.net/problem/UVA-1605#author=0 /* 问题 设计一个包含若干层的联合国大厦,其中每一层都是等大的网格,每个格子分配给一个国家 ...
- Selenium使用总结(Java版本)
硒在最近的发展中被广泛应用,因为它以前没有被使用过,并且已经走了太多的坑.这是一张唱片. 1.环境配置 配置要点: 1.Web驱动程序应该与浏览器版本相对应,chrome使用chrome驱动程序和ch ...
- Java - HashSet源码解析
java提高篇(二四)-----HashSet 一.定义 public class HashSet<E> extends AbstractSet<E> implements S ...
- 1-初识java
目录 java 历史 Java 平台 Java 开发环境 Java 运行原理[简] Java 历史 这里不详细记录java的历史,只是标记出时间点和事件. 时间点 事件 1991 Sun公司成立Gre ...
- tensorflow模型的保存与恢复
1.tensorflow中模型的保存 创建tf.train.saver,使用saver进行保存: saver = tf.train.Saver() saver.save(sess, './traine ...
- js-ES6学习笔记-module(4)
1.<script>标签打开defer或async属性,脚本就会异步加载.渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令. defer与asyn ...
- 你真的理解PeopleSoft的Web概要(web profile)嘛
Web概要通过配置门户相关属性来控制门户的所有行为. 在PS系统中可以创建多个web概要,你可以通过不同的web概要来让用户路由到一个特定的web概要来控制超时,外观,缓存设置等.例如,通过Peopl ...
- Vue + WebPack + Typescript初学者VSCode项目 (按需加载、跨域调试、await/async)
万事开头难,一个好的Hello World程序可以节省我们好多的学习时间,帮助我们快速入门.Hello World程序之所以是入门必读必会,就是因为其代码量少,简单易懂.但我觉得,还应该做到功能丰富, ...
- 一种通过async/await实现函数同步执行的方式
const testArr = [ () => { return new Promise((resolve, reject) => { setTimeout(()=> { alert ...