[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个位置上的拓扑序列的个数.在求节 ...
随机推荐
- Cglib invoke以及invokeSuper的一点区别
简单记录下,解决的一个问题,Cglib的invoke和invokeSuper的区别: 简而言之,invoke方法调用的对象没有增强过,invokeSuper方法调用的对象已经是增强了的,所以会再走一遍 ...
- maven国内aliyun镜像
打开maven安装目录下conf文件夹的settings.xml文件 配置本地仓库 <localRepository>D:/maven/repository</localReposi ...
- python学习之老男孩python全栈第九期_day002知识点总结
1. 格式化输出: (1) %(占位符) s(str字符串) d(digit数字) (2) 想单纯输入%,需要输入两个%(%和占位符冲突),前面的%相当于转义. 2. while else循环: (1 ...
- linux上,mysql使用聚合函数group by 时报错:SELECT list is not in GROUP BY clause and contains nonaggre的问题
之前在windows上测试是可以正常使用的,但是上传到Linux上后,就报错: Expression # of SELECT list is not in GROUP BY clause and co ...
- CentOS6.5搭建ldap及pdc的过程
linux centos6.5,,配置的是本地yum,采用光盘自带的rpm包进行安装 安装openldap server 以及client yum install openldap-server ...
- CCNA学习笔记(1) IOS操作系统 路由器 交换机 启动 自检 以及部分命令
注意:以下内容是以思科为学习环境 IOS操作系统启动: 路由和交换机和个人电脑启动没有区别,都会发送新号表示启动状态,也会进入系统自检.只得注意的是:1.一长两短的响声是显卡报警. 2.一声长鸣是内存 ...
- 前后端分离(手) -- mock.js
前言: 本篇博文昨天七夕写的,一天下来被虐得体无完肤,苦逼的单身狗只能学习,对!我爱学习,关掉朋友圈,并写了一篇博文发泄发泄.这次写mock.js的使用,能使前后端分离,分离,分离,重要的是说三遍. ...
- Java java jdk在Linux下安装与环境变量的配置
java jdk在Linux下安装与环境变量的配置 by:授客 QQ:1033553122 linux环境:CentOS-6.0-x86_64-bin-DVD1.iso [root@localhost ...
- create-react-app脚手架的安装和目录结构介绍
1.对脚手架的初步了解 编程领域中的“脚手架”指的是能够快速搭建项目“骨架”的一类工具,例如大多数的React项目都有src,public,webpack配置文件等等,而src目录中又包含compon ...
- window下安装RabbitMQ
RabbitMQ: MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.消 ...