bzoj3167 [Heoi2013]Sao

传送门

这题神坑啊……明明是你菜

首先大家都知道原题等价于给每个点分配一个$1$~$n$且两两不同的权值,同时还需要满足一些大于/小于关系的方案数。

先看一眼数据范围,既然写明了$n\le 1000$,那就应该是什么$O(n^2)$的做法了。显然这个东西只能是个DP,考虑到题中给出的是一个树形结构,那么就可以利用子树的相对独立性进行DP:设$f_{i,j}$表示以$i$为根的子树中有$j$个点的权值大于$i$的权值时的方案数,显然最终答案就是$\sum_{i}f_{root,i}$。

然后考虑怎么求出答案。通常树形DP都是自底向上逐个合并来得到每个点的DP值的,但注意这个DP无法做到像普通的树形DP一样可以快速(比如$O(1)$或者O(log^2 n)之类)合并。不过这个DP还是可以比较高效地合并的,设要合并的两个子树大小分别为$n,m$,那么我们就可以通过枚举每一对$(i,j)$对应的$f_{\dots,i}$和$f_{\dots,j}$对合并后的DP数组的贡献来在$O(nm)$的时间内得到它们合并后的DP数组。

具体的合并过程就不写了,大体思路是先枚举最后$i$的权值在整个子树中的排名,然后枚举另一棵子树中有几个点权值比$i$的权值小,最后换元得到枚举$(i,j)$的形式。顺便一提,这个DP方程还需要一个前缀和优化才能做到$O(nm)$合并,还有大于和小于两种情况需要分开处理。

 1 /**************************************************************
 2     Problem: 3167
 3     User: _Angel_
 4     Language: C++
 5     Result: Accepted
 6     Time:3984 ms
 7     Memory:8792 kb
 8 ****************************************************************/
 9 #include<cstdio>
10 #include<cstring>
11 #include<algorithm>
12 #include<vector>
13 using namespace std;
14 const int maxn=1005,p=1000000007;
15 int C[maxn][maxn];
16 struct DP{
17     int f[maxn],n;
18     void clear(){
19         memset(f,0,sizeof(f));
20         n=1;
21         f[0]=1;
22     }
23     DP &operator+=(const DP &b){
24         static int g[maxn];
25         memset(g,0,sizeof(g));
26         for(int i=0;i<n;i++)for(int j=1,tmp=0;j<=b.n;j++){
27             tmp=(tmp+b.f[j-1])%p;
28             g[i+j]=(g[i+j]+(long long)f[i]*tmp%p*C[i+j][j]%p*C[n+b.n-i-j-1][b.n-j]%p)%p;
29         }
30         memcpy(f,g,sizeof(f));
31         n+=b.n;
32         return *this;
33     }
34     DP &operator*=(const DP &b){
35         static int g[maxn];
36         memset(g,0,sizeof(g));
37         int sum=0;
38         for(int j=0;j<b.n;j++)sum=(sum+b.f[j])%p;
39         for(int i=0;i<n;i++)for(int j=0,tmp=sum;j<b.n;j++){
40             g[i+j]=(g[i+j]+(long long)f[i]*tmp%p*C[i+j][j]%p*C[n+b.n-i-j-1][b.n-j]%p)%p;
41             tmp=(tmp-b.f[j]+p)%p;
42         }
43         memcpy(f,g,sizeof(f));
44         n+=b.n;
45         return *this;
46     }
47 }f[maxn];
48 void dfs(int);
49 vector<int>G[maxn];
50 vector<bool>W[maxn];
51 int T,n,prt[maxn];
52 int main(){
53     C[0][0]=1;
54     for(int i=1;i<=1000;i++)for(int j=0;j<=i;j++){
55         C[i][j]=C[i-1][j];
56         if(j)C[i][j]=(C[i][j]+C[i-1][j-1])%p;
57     }
58     scanf("%d",&T);
59     while(T--){
60         scanf("%d",&n);
61         memset(prt,0,sizeof(prt));
62         for(int i=1;i<=n;i++){
63             G[i].clear();
64             W[i].clear();
65             f[i].clear();
66         }
67         for(int i=1,x,y;i<n;i++){
68             char c;
69             scanf("%d %c%d",&x,&c,&y);
70             x++;
71             y++;
72             G[x].push_back(y);
73             W[x].push_back(c==‘>‘);
74             G[y].push_back(x);
75             W[y].push_back(c==‘<‘);
76         }
77         dfs(1);
78         int ans=0;
79         for(int i=0;i<n;i++)ans=(ans+f[1].f[i])%p;
80         printf("%d\n",ans);
81     }
82     return 0;
83 }
84 void dfs(int x){
85     for(int i=0;i<(int)G[x].size();i++)if(G[x][i]!=prt[x]){
86         prt[G[x][i]]=x;
87         dfs(G[x][i]);
88         if(W[x][i])f[x]+=f[G[x][i]];
89         else f[x]*=f[G[x][i]];
90     }
91 }

时间: 2024-10-09 10:36:16

bzoj3167 [Heoi2013]Sao的相关文章

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

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

[HEOI2013]SAO

[HEOI2013]SAO 这道题是个不错的计数题,考察了调换求和顺序再前缀和优化,难点在状态设计,比较考察思维. 一句话题意:给你一棵数,树边为有向边,求其拓扑序数. 对DAG求拓扑数是一个NP问题,但是这里保证是一棵树,所以我们可以用树形DP来求解. 状态的设计上,光设结点编号\(u\)不够,还需要设计一维\(i\)表示结点\(u\)在以\(u\)为根的子树中的拓扑序的第\(i\)位,这样我们就可以写转移方程了. 对于\(u \rightarrow v\) \[ F'[u][k] = \Si

[HEOI2013]SAO ——计数问题

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

BZOJ 3167: [Heoi2013]Sao

Description 一个排列,满足一些限制,形成一个树形结构,求方案数\(T\leqslant 5,n\leqslant 1\times 10^3\) Solution 树形DP. \(f[i][j]\)表示\(i\)是在他的子树中排名为\(j\). 也是暴力合并信息,复杂度分析同上题. Code /************************************************************** Problem: 3167 User: BeiYu Language

P4099 [HEOI2013]SAO

传送门 n 个关卡有 n-1 个限制 所以这些限制构成一颗树 考虑树形DP 对一颗子树单独考虑 考虑有多少种顺序 设 f [ i ] 表示节点 i 的子树的总方案数 考虑儿子节点如何与父节点合并 发现父子之间有限制条件,所以 f 多加一维 f [ i ] [ j ] 表示节点 i 在子树中排第 j 时的方案数 子树合并时就可以看成两个序列合并 比如像这样(x和v是的父节点): { ,,x,, } + { ..v ...}  =  { ,..,v .x  ,  .., } 上图就是 f [ x ]

[提升性选讲] 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)

转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7337179.html 树形DP是一种在树上进行的DP相对比较难的DP题型.由于状态的定义多种多样,因此解法也五花八门,经常成为高水平考试的考点之一. 在树形DP的问题中,有这样一类问题:其数据范围相对较小,并且状态转移一般与两两节点之间的某些关系有关. 今天,我们就来研究一下这类型的问题,并且总结一种(相对套路的)解决大多数类型题的思路. 首先,我们用一道相对简单的例题来初步了解这个类型题的大致思路,以及一

[BZOJ4824][CQOI2017]老C的键盘(树形DP)

4824: [Cqoi2017]老C的键盘 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 193  Solved: 149[Submit][Status][Discuss] Description 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序 在某种神奇力量的驱使之下跑得非常快.小 Q 也是一个程序员.有一天他悄悄潜入了老 C 的家中,想要看看这个 键盘究竟有何妙

DP常用优化

DP常用优化 一.前缀和优化 当遇到类似:\(f[i] = \sum_{j = k}^{i} g[j]\)的转移时,可以通过预处理出\(g[i]\)的前缀和\(s[i]\),将\(O(n)\)的求和转换为\(O(1)?\)的操作. [HAOI2009]逆序对数列 [HAOI2008]木棍分割 二分答案+dp P4099 [HEOI2013]SAO 树形dp 二.决策单调性--单调队列优化 接下来几种优化方法主要是对1d/1d dp的优化,其中xd/yd dp指的是状态数有\(n^x\)种,每个状

【BZOJ】【3166】【HEOI2013】Alo

可持久化Trie+set Orz zyf…… 搞区间中次大值不好搞,那么我们就反过来,找一个数,然后看它在哪些区间里是次大值…… (然而事实上我们并不用真的把这个区间具体是什么找见,只要知道它可以跟哪一段数搞Xor就可以了! 而这个区间就是……左边第二个比他大的数的位置+1 ~ 右边第二个比它大的数的位置-1 这中间所有数都可以跟它搞Xor= =,我们总能找到一个相应的区间…… (我一开始理解成,这个区间就是我们要找的,a[i]为次大数的区间,然而这不是左边有一个比它大的,右边也有一个比它大的吗