HDU 6338 Problem G. Depth-First Search(Treap平衡树+dfs)

题目链接

Problem G. Depth-First Search

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 643    Accepted Submission(s): 140


 

Problem Description

Kazari is learning depth-first search. More precisely, she is doing an experiment about it.
Consider an unrooted tree with n vertices and an empty array called A.
She randomly chooses a vertex s as root and starts from s to walk around, following the rules below.
* When she enters a vertex x for the first time, she append x to A at once.
* If some adjacent vertex has not been visited, she randomly chooses one and walks into it.
* If all adjacent vertices have been visited,
* If she is at root, the experiment is done.
* If she is not at root, she walks into the vertex which is the most nearest to root.
Among all possible arrays that A is likely to be finally, Kazari wishes to count how many of them is lexicographically smaller than the given array B. Since the answer is too large, print it modulo 109+7.

Input

The first line of the input contains an integer T denoting the number of test cases.
Each test case starts with a positive integer n (∑n≤106), denoting the number of vertices.
The second line contains n integers B1,B2,...,Bn (1≤Bi≤n,∀i≠j,Bi≠Bj).
Each of next n−1 lines contains two integers u,v, representing an edge (u,v) on the tree.

Output

For each test case, print a non-negative integer denoting the answer modulo 109+7.

Sample Input

 

2

5

2 1 3 5 4

1 2

2 3

2 4

5

6

6 4 5 3 2 1

1 2

2 3

3 4

4 5

5 6

 

 

Sample Output

 

3

9

 


题意:
给你一棵n个节点的树和一个1-n的排列,问你树有多少种dfs序的字典序小于该排列。
注意这个排列不一定是某一个dfs序

解析:
推荐还是看一下dls的讲解再来看本文视频链接

一棵以x为根的有根树的dfs序方案数是deg[x] *\prod _{v \in tree(x)}{(deg[v]-1)!},v是树x的全部节点(包括x)

然后我们遍历以不同点为根的树,发现\prod _{v \in tree}{(deg[v]-1)!}是公共的部分,就是树种全部节点的度数-1的阶乘再乘再一起

那么我们就可以发现以任意一个节点为根的树的dfs序方案数就是根的度数*树中所有节点的度数-1阶乘的累乘

那么我们就可以算以x为dfs序的开头的方案数 x \in [1,B[1]),即以x为根的dfs序的方案数。

那么接下来需要算的就是以B[1]为根的小于B的dfs序的数量。

在遍历之前我们还应该让res=res*d[B[1]],即让res=以B[1]为根的所有dfs序的数量。

后面的res的状态都会是,除已确定的节点外,树的dfs序的数量。

一开始B[1]确定了,所以res就需要等于以B[1]为根的所有dfs序的数量。

这一步我们就是通过逐位遍历B[]求得的,在dfs中,我们永远按照B[]中的顺序来遍历树

如果B中的顺序不符合任意一种dfs序,那么情况很好判断,下面会讲

假定当前我们遍历到b[m]节点,那么接下来,我们需要看b[m]的儿子中有没有小于b[m+1]的节点

HDU 6338 Problem G. Depth-First Search(Treap平衡树+dfs)_第1张图片

我们以上面这棵树为例,假定B是1 2 5 6 4.......

我们遍历到2节点,接下来看2的儿子中有多少小于5的,发现有2个

(这里使用Treap平衡树来查询的,某一个点的孩子节点中,小于某一个值的点的数量,rank操作)

对于任意确定哪一个,效果都是一样的。假定确定了3,那么4,5及剩下的节点就是可以任意选的;

确定4,3,5及剩下的节点就是可以任意走的(不过这个走一定是按照dfs顺序进行的)。

因为在这一位3已经小于5了,剩下的节点如何整个序列都是小于B的

res就表示除已确定1,2外,剩下的节点任意走的dfs序数量。这里又确定了一个点3/4。

所以此时第3位确定为3/4,对答案的贡献为t*\frac{res*(deg[x]-1)!}{deg[x]!}

算完第三位小于B[3]的贡献后,我们就要往5走,因为这里我们是遍历B逐位确定答案的,所以一定是按照B的顺序走

这里就分为两种情况了。

1.5是2的孩子节点,那么我们就自然地往5走

2.如果5不是2的孩子节点,并且2孩子节点的数量>0,那么我们其实就不用在遍历下去了,因为答案都已经算过了。

因为dfs序中2后面的那一位永远都不可能是5。

如果2的孩子节点都小于5,t=|son(2)|因为是按照dfs序进行的,所以2后面的一定可以任意走了,即2后面的那些位都是任意的

如果2的孩子节点都大于5,t=0 那么2后面不可能又小于B的dfs序,

如果2的孩子一部分小于5,t=小于5的节点数量,2后面那一位只能是那些小于5的点,再后面的那些位任意。

所以这些情况,我们都在上面计算过了,不需要再讨论了。并且这里出现这种原因是,题目规定的是dfs序,所以2有孩子的话,

一定是往孩子走的,这样5不在孩子里面,剩下的就任意了。举B=1,9,7,.....的例子,在上面走一下应该就明白了。

那么5在2的孩子节点,我们在走下去之前,还要把5从2的可选孩子节点中删除,对应更新d[2](-1),res,以2为根的Treap平衡树(删除操作,删除5)

因为你一旦选了5,5就不再变成可选节点了,5就确定了。

根据上面的例子1 2 5 6 4,当向5往下搜后,发现到头了,那么就要往回搜(符合dfs序性质),重新到2

此时找小于6的节点的数量,为2个(此时确定的点 1 2 5),所以这里5是不能用于贡献答案的,因为他已经被确定了

如果B是满足dfs序的,那么我们就需要遍历整棵树得到答案,如果不满足,那我们就只会遍历到不满足dfs序的那个点为止就

停止了。

这里我有一点没有搞懂,这里dfsB的时候,需要用x=0剪枝,不然会T....但我试了几组数据,好像没有到x=0的情况....不过会T是真的.......

Treap讲解

Treap详解1 

Treap原理

Treap数组模板

//
//  main.cpp
//  treap_prac1
//
//  Created by Candy on 26/11/2016.
//  Copyright © 2016 Candy. All rights reserved.
//

#include
#include
#include
#include
#include
#include
using namespace std;
#define lc t[x].l
#define rc t[x].r
#define pb push_back
typedef long long ll;
const int MAXN=1e6+5;
const ll MOD = 1e9+7;
int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
struct node{
    int l,r,v,w,size,rnd;
}t[MAXN*2];
int cnt=0,rt[MAXN];
int B[MAXN];
ll jc[MAXN];
ll inv[MAXN];
ll res,ans;
int d[MAXN],fa[MAXN];
vector edge[MAXN];


inline void update(int x){t[x].size=t[lc].size+t[rc].size+t[x].w;}
inline void rturn(int &x){
    int c=lc;lc=t[c].r;t[c].r=x;
    t[c].size=t[x].size;update(x);x=c;
}
inline void lturn(int &x){
    int c=rc;rc=t[c].l;t[c].l=x;
    t[c].size=t[x].size;update(x);x=c;
}
void ins(int &x,int v){
    if(x==0){
        cnt++;x=cnt;
        t[cnt].l=t[cnt].r=0;t[cnt].size=t[cnt].w=1;
        t[cnt].v=v;t[cnt].rnd=rand();
        return;
    }
    t[x].size++;
    if(t[x].v==v) {t[x].w++;return;}
    if(v1){t[x].w--,t[x].size--;return;}
        if(lc*rc==0) x=lc+rc;
        else if(t[lc].rndt[lc].size+t[x].w) return kth(rc,k-t[lc].size-t[x].w);
    else return t[x].v;
}
//int ans;
void pre(int x,int v){
    if(x==0) return;
    if(v>t[x].v) ans=x,pre(rc,v);
    else pre(lc,v);
}
void suf(int x,int v){
    if(x==0) return;
    if(vn||flag||!x) return;    //!!!!!!!????????
    if(d[x])   //如果不是叶子节点
    {
        int tt=rnk(rt[x],B[m+1]-1);   //x的孩子节点中小于b[m]的节点数量
        ans=(ans+res*inv[d[x]]%MOD*jc[d[x]-1]%MOD*tt%MOD)%MOD;
        if(fa[B[m+1]]!=x) {flag=1;return;}    //如果B不满足在x的dfs序,那么所有的情况都已经计算完了
        m++;   //当前已经计算完前m位的答案了
        //将已经确定的节点b[m]从树中删除,因为他不会再贡献答案了,对于后面的答案b[m]的位置已经固定了
        res=res*inv[d[x]]%MOD*jc[d[x]-1]%MOD;  //贡献方案数res相应变化
        d[x]--;
        del(rt[x],B[m]);
        dfs(B[m]);
    }
    else dfs(fa[x]);    //dfs遍历到头,向其他子树中寻找b[]剩下的元素
}

int main(int argc, const char * argv[]) {
    srand(222);
    int t=read(),op,x;
    init();
    while(t--){
        scanf("%d",&n);
    for(int i=0;i<=n;i++) d[i]=0,fa[i]=0,rt[i]=0,edge[i].clear();
    for(int i=1;i<=n;i++)
        scanf("%d",&B[i]);
    for(int i=1;i

这里是pbds库版本pbds库

#include
#include
#include
#include
#include
#include
#include 
#include 
#include 
using namespace __gnu_pbds;
using namespace std;
#define lc t[x].l
#define rc t[x].r
#define pb push_back
typedef long long ll;
const int MAXN=1e6+5;
const ll MOD = 1e9+7;
int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}

int B[MAXN];
ll jc[MAXN];
ll inv[MAXN];
ll res,ans;
int d[MAXN],fa[MAXN];
vector edge[MAXN];
tree,rb_tree_tag,tree_order_statistics_node_update> rt[MAXN];





void init()
{
    ll p=1;
    jc[0]=1;
    jc[1]=1;
    inv[0]=1;
    inv[1]=1;
    for(int i=2;in||flag||!x) return;    //!!!!!!!????????
    if(d[x])   //如果不是叶子节点
    {
        int tt=rt[x].order_of_key(B[m+1]); //这里要注意的是,求kth(find_by_order)返回的是迭代器,求rank返回的是值,两者都是从0开始计算的。
        //int tt=rnk(rt[x],B[m+1]-1);   //x的孩子节点中小于b[m]的节点数量
        ans=(ans+res*inv[d[x]]%MOD*jc[d[x]-1]%MOD*tt%MOD)%MOD;
        if(fa[B[m+1]]!=x) {flag=1;return;}    //如果B不满足在x的dfs序,那么所有的情况都已经计算完了
        m++;   //当前已经计算完前m位的答案了
        //将已经确定的节点b[m]从树中删除,因为他不会再贡献答案了,对于后面的答案b[m]的位置已经固定了
        res=res*inv[d[x]]%MOD*jc[d[x]-1]%MOD;  //贡献方案数res相应变化
        d[x]--;
        //del(rt[x],B[m]);
        rt[x].erase(B[m]);
        dfs(B[m]);
    }
    else dfs(fa[x]);    //dfs遍历到头,向其他子树中寻找b[]剩下的元素
}

int main(int argc, const char * argv[]) {
    srand(222);
    int t=read(),op,x;
    init();
    while(t--){
        scanf("%d",&n);
    for(int i=0;i<=n;i++) d[i]=0,fa[i]=0,rt[i].clear(),edge[i].clear();
    for(int i=1;i<=n;i++)
        scanf("%d",&B[i]);
    for(int i=1;i

你可能感兴趣的:(树,dfs,HDU,ACM)