3167: [Heoi2013]Sao

Time Limit: 30 Sec  Memory Limit: 256 MB
Submit: 96  Solved: 36
[Submit][Status][Discuss]

Description

WelcometoSAO(StrangeandAbnormalOnline)。这是一个VRMMORPG,
含有n个关卡。但是,挑战不同关卡的顺序是一个很大的问题。
有n–1个对于挑战关卡的限制,诸如第i个关卡必须在第j个关卡前挑战,或者完成了第k个关卡才能挑战第l个关卡。并且,如果不考虑限制的方向性,那么在这n–1个限制的情况下,任何两个关卡都存在某种程度的关联性。即,我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集之间没有任何限制。

Input

第一行,一个整数T,表示数据组数。对于每组数据,第一行一个整数n,表示关卡数。接下来n–1行,每行为“i sign j”,其中0≤i,j≤n–1且i≠j,sign为“<”或者“>”,表示第i个关卡必须在第j个关卡前/后完成。

Output

对于每个数据,输出一行一个整数,为攻克关卡的顺序方案个数,mod
1,000,000,007输出。

Sample Input


2
5
0<2
1<2
2<3
2<4
4
0<1
0<2
0<3

Sample Output

4
6

HINT

对于100%的数据有T≤5,1≤n≤1000。

Source

 

[Submit][Status][Discuss]

请先允许我这个SAO脑残粉吐槽一下,下次出题是不是就该叫ALO,GGO什么的了?我封弊者绝不会允许这种事情发生的……

下面是正经的题解——

既然题目里都说了所有的限制关系忽略方向会形成一棵树,那不搞个树形DP就说不过去了不是?

DP[i][j]表示,i的子树(包含i)在满足其内部所有限制条件下,i在这个序列中位于第j个位置的方案数。

发现,其实合并操作只是作用于两个一维数组,和i具体是啥并没什么关系,当然这是题外话,并不影响解题。(我只是想说可以写个 struct Data,然后开个 array<Data, N> 什么的,再定义个运算符 operator + 什么可能会好写好想很多)

然后先考虑暴力的转移方式吧,下面先给出伪代码,再作详细解释。

for i from  to siz[u] do
for j from to siz[v] do
for k from i+j to i+siz[v] do
newDP[u][k] += DP[u][i]*DP[v][j]*G[i-][k-i]*G[siz[u]-i][siz[v]-k+i]
DP[u] = newDP[u]

这是对于一条树边(u,v),意义为u必须出现在v的后面,的合并操作。其中DP数组的意义同之前的介绍,但是需要注意两点:

1.DP[u]是已经合并u节点和其之前已经遍历到的子树后的DP数组。

2.newDP[u]是一个新开的临时数组,因为我们在转移的时候还要用到DP[u]的信息,所以新开一个数组记录转移答案,转移完再赋给DP[u](当然最后你也可以选择通过适当调整转移的顺序省去这个数组,但是这样更节约脑细胞不是吗)。

然后来说一下G[][]数组,这是一个预处理出来的数组,G[i][j]代表把一个长度为i的有序数列和一个长度为j的有序数列合并成一个长度为i+j的有序数列,其中两个数列本来的元素的相对位置不改变。其预处理也很简单——

for i from  to maxSize do
G[][i] =
G[i][] =
for i from to maxSize do
for j from to maxSize do
G[i][j] = G[i-][j] + G[i][j-]

也就是在每一步枚举一下是把第一个序列的下一个元素加入最终序列还是第二个序列的下一个元素加入最终序列,注意序列长度为0也是有意义的。

然后回头看上边的转移代码。

其中i是枚举的u在其原来序列中的位置,j是枚举的v在其原来序列中的位置,k是枚举一下u在最终序列中的位置。我们发现因为之要求u出现在v之后,所以u最终的位置不一定是i+j,可能比i+j更靠后一些,相对的v所在序列的一些v之后的元素可以填充到u之前,这依然是合法的,而且需要通过DP[u][i]和DP[v][j]转移。可以理解那个四项乘法算式的意义为两个子树的方案数之积x把u原来序列中在u之前的元素和v原来序列中现在在u之前的元素组成新的序列的方案数x把u原来序列中在u之后的元素和v原来序列中现在在u之后的元素组成新序列的方案数。

现在已经有了暴力的转移方法,写一写会发现可以水样例,那基本就是正确的喽?然后粗略估计一下,应当过不去N=1000的数据(显然的好嘛!)。考虑优化方法。

显然,这类问题一般可以把k提到j之前什么的(或做一些其他的循环顺序的改变)来改变式子,往往有意想不到的效果。

我们改成先枚举k,再枚举j,推一下新的算式。(某大爷就推错了这一步,23333)

for i from  to siz[u] do
for k from i+ to siz[v]+i do
for j from to k-i do
newDP[u][k] += DP[u][i]*DP[v][j]*G[i-][k-i]*G[siz[u]-i][siz[v]-k+i]

然后发现j的枚举是没必要的,因为对于一定的i和k,G[i-1][k-i]*G[siz[u]-i][siz[v]-k+i]的值是一定的,DP[u][i]的值是一定的,ΣDP[v][j]可以通过维护DP[v][j]的前缀和O(1)查询。

设$sum[v][d]=\sum_{j<=d}{DP[v][j]}$,代码成了这个样子——

for i from  to siz[u] do
for k from i+ to siz[v]+i do
newDP[u][k] += DP[u][i]*sum[v][k-i]*G[][]*G[][]

其中G数组的下标我就省略了,同上面一模一样。

然后证明当前代码的全局复杂度是$O(N^{2})$的。

i的枚举是u之前已经访问到的子树的数量,k的枚举是v子树的数量,相当于枚举了v子树和u之前子树的所有点对(其中一个点在v的子树,一个在u之前子树)。这些点对只会在其LCA处(也就是u)被枚举,所以不会重复枚举。而一个N个点的树,点对数量显然是$O(N^{2})$的,完结撒花~~~

 #include <cstdio>
#include <cstring> const int mxn = ;
const int mxm = ;
const int mod = 1E9 + ; template <class T>
inline void swap(T &a, T &b)
{
T c;
c = a;
a = b;
b = c;
} #define add(a,b) ((((a)+(b))%mod+mod)%mod)
#define mul(a,b,c,d) (1LL*(a)*(b)%mod*(c)%mod*(d)%mod) int n;
int cas;
int tot; int hd[mxn];
int to[mxm];
int nt[mxm]; inline void addEdge(int a, int b)
{
nt[tot] = hd[a], to[tot] = b, hd[a] = tot++;
nt[tot] = hd[b], to[tot] = a, hd[b] = tot++;
} int cal[mxn][mxn]; inline void prework(void)
{
for (int i = ; i < mxn; ++i)
{
cal[i][] = ;
cal[][i] = ;
} for (int i = ; i < mxn; ++i)
for (int j = ; j < mxn; ++j)
cal[i][j] = add(cal[i - ][j], cal[i][j - ]);
} int sz[mxn];
int dp[mxn][mxn];
int sm[mxn][mxn];
int tp[mxn][mxn]; void dfs(int u, int f)
{
sz[u] = dp[u][] = ; for (int i = hd[u], v; ~i; i = nt[i])
if ((v = to[i]) != f)
{
dfs(v, u); memset(tp[u], , sizeof tp[u]); if (i & )
{
for (int j = ; j <= sz[u]; ++j)
if (j < sz[u] + - j)
swap(dp[u][j], dp[u][sz[u] + - j]); for (int j = ; j <= sz[v]; ++j)
if (j < sz[v] + - j)
swap(dp[v][j], dp[v][sz[v] + - j]);
} {
for (int j = ; j <= sz[v]; ++j)
sm[v][j] = add(sm[v][j - ], dp[v][j]); for (int j = ; j <= sz[u]; ++j)
for (int k = j + ; k <= sz[v] + j; ++k)
tp[u][k] = add(tp[u][k], mul(dp[u][j], sm[v][k - j], cal[j - ][k - j], cal[sz[u] - j][sz[v] - k + j]));
} memcpy(dp[u], tp[u], sizeof dp[u]); sz[u] += sz[v]; if (i & )
{
for (int j = ; j <= sz[u]; ++j)
if (j < sz[u] + - j)
swap(dp[u][j], dp[u][sz[u] + - j]);
}
}
} signed main(void)
{
for (prework(), scanf("%d", &cas); cas--; tot = )
{
scanf("%d", &n); memset(hd, -, sizeof hd);
memset(dp, , sizeof dp);
memset(sm, , sizeof sm); for (int i = ; i < n; ++i)
{
int a, b; char c; scanf("%d", &a); do
c = getchar();
while (c != '>' && c != '<'); scanf("%d", &b); if (c == '>')
addEdge(++a, ++b);
else
addEdge(++b, ++a);
} dfs(, ); int ans = ; for (int i = ; i <= n; ++i)
ans = add(ans, dp[][i]); printf("%d\n", ans);
}
}

另外,这题很坑的一点就是样例中的限制关系是不在符号前后加空格的,但是貌似TestInput是有的,所以像下面这么写都会RE。

scanf("%d%c%d", &a, &c, &b);

我就栽了2次,佩服自己的机智能猜到是这么RE的。

@Author: YouSiki

BZOJ 3167: [Heoi2013]Sao的更多相关文章

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

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

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

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

  3. [BZOJ 3167][HEOI 2013]SAO

    [BZOJ 3167][HEOI 2013]SAO 题意 对一个长度为 \(n\) 的排列作出 \(n-1\) 种限制, 每种限制形如 "\(x\) 在 \(y\) 之前" 或 & ...

  4. 【BZOJ3167】[HEOI2013]SAO(动态规划)

    [BZOJ3167][HEOI2013]SAO(动态规划) 题面 BZOJ 洛谷 题解 显然限制条件是一个\(DAG\)(不考虑边的方向的话就是一棵树了). 那么考虑树型\(dp\),设\(f[i][ ...

  5. P4099 [HEOI2013]SAO

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

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

    P4099 [HEOI2013]SAO 我们设$f[u][k]$表示以拓扑序编号为$k$的点$u$,以$u$为根的子树中的元素所组成的序列方案数 蓝后我们在找一个以$v$为根的子树. 我们的任务就是在 ...

  7. 【BZOJ3167/4824】[Heoi2013]Sao/[Cqoi2017]老C的键盘

    [BZOJ3167][Heoi2013]Sao Description WelcometoSAO(StrangeandAbnormalOnline).这是一个VRMMORPG,含有n个关卡.但是,挑战 ...

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

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

  9. 【做题记录】 [HEOI2013]SAO

    P4099 [HEOI2013]SAO 类型:树形 \(\text{DP}\) 这里主要补充一下 \(O(n^3)\) 的 \(\text{DP}\) 优化的过程,基础转移方程推导可以参考其他巨佬的博 ...

随机推荐

  1. 开启Node.js的大门

    其实也没什么好说的,简而言之,就是如何配置node.js环境,然后进行开发.博主最近比较堕落,觉得什么事情也没有就不知道想干什么,想融入一些事情又觉得没大神指引,于是自娱自乐开始自己玩node.js, ...

  2. mfc CListCtrl 报表格式

    知识点: CListCtrl报表格式 CListCtrl报表格式添加列 CListCtrl报表格式添加行 CListCtrl报表格式设置单元格 一.CListCtrl报表格式 类名:SysListVi ...

  3. 小内存VPS apache并发控制参数prefork调优

    小内存VPS优化(使用wdcp lnamp一键包安装环境的情况下): 1.主要优化perfork模式下几个参数,防止开启过多的httpd进程占用大量内存导致内存满:在wdcp下修改的httpd配置文件 ...

  4. libgdx学习记录17——照相机Camera

    照相机在libgdx中的地位举足轻重,贯穿于整个游戏开发过程的始终.一般我们都通过Stage封装而间接使用Camera,同时我们也可以单独使用Camera以完成背景的移动.元素的放大.旋转等操作. C ...

  5. 记一次 java 连接 linux ssh服务 权限验证失败的原因和解决过程

    下面的问题我是通过之前的ssh测试类找出原因的,因为我的测试类跑通了,但是程序跑不通,看了一下源码发现还有一处没有进行解密,所以才会权限验证失败. // 出现权限验证失败的原因就在这里,因为老板要求对 ...

  6. R实战 第六篇:数据变换(aggregate+dplyr)

    数据分析的工作,80%的时间耗费在处理数据上,而数据处理的主要过程可以分为:分离-操作-结合(Split-Apply-Combine),也就是说,首先,把数据根据特定的字段分组,每个分组都是独立的:然 ...

  7. 软件测试----H模型

    H模型将测试活动完全独立出来,形成一个完整的流程,同时将测试准备和测试执行清晰表现出来. 测试流程: --测试准备:所有测试活动的准备判断是否到测试就绪点. --测试就绪点:测试准入准则,即是否可以开 ...

  8. 初次接触Dynamics 365

    最近项目上需要用到微软的Dynamics 365 这个产品,Bing上搜索了一下,看了很多大佬在博客上分享了使用Dynamics 365的经验,简单了解了Dynamics 365 是什么,也有很多大企 ...

  9. PS官方正式中文版(搬砖分享)

    https://pan.baidu.com/s/1c3IdQq0 PS官方正式中文版(搬砖分享) 注意事项: 1.安装开始前请先断网,在成功破解激活前请全程断网: 2.安装完成后先试运行软件一次,然后 ...

  10. 现代OpenGL渲染管线介绍

    原文摘选自 现代OpenGL渲染管线介绍 此文对最新的OpenGL做一个简单的介绍,如有理解错误,敬请指正.英文原文: https://glumpy.github.io/modern-gl.html ...