[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个位置上的拓扑序列的个数.在求节 ...
随机推荐
- 网络之Json生成解析
// // ViewController.m // Json // // Created by City--Online on 15/4/28. // Copyright (c) 2015年 CYW. ...
- 【WePY小程序框架实战四】-使用async&await异步请求数据
[WePY小程序框架实战一]-创建项目 [WePY小程序框架实战二]-页面结构 [WePY小程序框架实战三]-组件传值 async await 是对promise的近一步优化,既解决了promise链 ...
- SqlDataAdapter 对datagridview进行增删改(A)
这种方法主要是双击datagridview单元格,直接进行添加,修改,删除,在实际开发中并不太常用,另一种方法下一次在具体陈述. using System; using System.Collecti ...
- SQl常用语句总结(持续更新……)
创建示例数据库 USE master; GO IF DB_ID (N'mytest') IS NOT NULL DROP DATABASE mytest; GO CREATE DATABASE myt ...
- Hibernate高效查询,只查询部分/指定字段
公司使用 DetachedCriteria detachedCriteria = DetachedCriteria.forClass(PeBulletin.class); detachedCriter ...
- EF 求和 GroupBy多个字段
GroupBy根据多个字段分组使用方式: 一.使用扩展方法 query.GroupBy(q => new { q.Year, q.Month }) .Select(q => new { Y ...
- 几点建议,让Redis在你的系统中发挥更大作用
Redis在很多方面与其他数据库解决方案不同:它使用内存提供主存储支持,而仅使用硬盘做持久性的存储:它的数据模型非常独特,用的是单线程.另一个大区别在于,你可以在开发环境中使用Redis的功能,但却不 ...
- Java对MySQL数据库进行连接、查询和修改(转)
Java对MySQL数据库进行连接.查询和修改 0. 一般过程: (1) 调用Class.forName()方法加载驱动程序. (2) 调用DriverManager对象的getConnection( ...
- HDU1814(2-SAT)
Peaceful Commission Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- Software-Defined Networking A Comprehensive Survey --阅读_day2
3. 什么是SDN?(WHAT IS SOFTWARE-DEFINED NETWORKING?) The term SDN was originally coined to represent the ...