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. laravel 5.5 《电商实战 》辅助函数

    Laravel 提供了很多 辅助函数,有时候我们也需要创建自己的辅助函数. 这里介绍了 tinker,一个laravel内置的php交互式控制台,方便调试php代码 php artisan tinke ...

  2. 20155229《网络对抗技术》Exp2:后门原理与实践

    实验预习 后门: 指绕过安全控制而获取对程序或系统访问权的方法.最主要目的就是方便以后再次秘密进入或者控制系统. 木马与后门的区别: 木马:通过欺骗用户的方法(包含捆绑,利用网页等)让用户不知不觉的安 ...

  3. Class does not Implement Equals——Code Correctness(代码正确性)

        系列文章目录:     使用Fortify进行代码静态分析(系列文章) class does not implement equals(类未能实现Equals方法)   示例:  protec ...

  4. 洛咕 P3756 [CQOI2017]老C的方块

    四染色,贼好想 一个弃疗图形刚好对应一个红-绿-黄-粉色路线(不要吐槽颜色) 就是裸的最小割,建图傻逼懒得写了 #include<bits/stdc++.h> #define il inl ...

  5. 蓝牙inquiry流程之Inquiry Complete处理

    inquiry流程一般持续有12s多,当inquiry完成的时候,设备端会上报一个Event: Inquiry Complete 上来,那协议栈是如何把这个事件上传到应用层的呢?本篇文章来分析一下其具 ...

  6. Java 多线程(三)之线程状态及其验证

    目录 线程状态 Thread.State 状态类型 定义 说明 状态转换 状态验证 「NEW」-> 「RUNNABLE」 -> 「TERMINATED」 「RUNNABLE」 -> ...

  7. 未能使用“Csc”任务的输入参数初始化该任务

    今天.NetCore2.1版本,建立Asp.net Core web应用程序项目时,报以下错误: 未能使用“Csc”任务的输入参数初始化该任务. “Csc”任务不支持“SharedCompilatio ...

  8. docker之故障问题解决方案

    1.报错如下一 Error response from daemon: driver failed programming external connectivity on endpoint lnmp ...

  9. Harbor 学习分享系列1 - centos7.4安装harbor1.5.2

    centos7.4安装harbor1.5.2 前言 本系列分享将Harbor有关教程:分享形式会以百度云盘的形式进行分享,主要教程将以markdown格式进行分享:建议使用markdownpad2这款 ...

  10. libimobiledevice --Mingw32交叉编译

    本文只描述 windows环境下的使用情况,linux平台基本雷同. 一.配置编译环境. (1)操作系统 :Windows10 (64bit). (2)类unix环境:Cygwin(64bit) 下载 ...