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

13 篇文章 0 订阅

题目链接

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]的节点

我们以上面这棵树为例,假定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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
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<int> 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(v<t[x].v){
        ins(lc,v);
        if(t[lc].rnd<t[x].rnd) rturn(x);
    }else{
        ins(rc,v);
        if(t[rc].rnd<t[x].rnd) lturn(x);
    }
}
void del(int &x,int v){
    if(x==0) return;
    if(t[x].v==v){
        if(t[x].w>1){t[x].w--,t[x].size--;return;}
        if(lc*rc==0) x=lc+rc;
        else if(t[lc].rnd<t[rc].rnd) rturn(x),del(x,v);
        else lturn(x),del(x,v);
    }else{
        t[x].size--;
        if(v<t[x].v) del(lc,v);
        else del(rc,v);
    }
}
int rnk(int x,int v){
    if(!x) return 0;
    if(t[x].v==v) return t[lc].size+1;
    if(v<t[x].v) return rnk(lc,v);
    else return t[lc].size+t[x].w+rnk(rc,v);
}
int kth(int x,int k){
    if(x==0) return 0;
    if(k<=t[lc].size) return kth(lc,k);
    else if(k>t[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(v<t[x].v) ans=x,suf(lc,v);
    else suf(rc,v);
}


void init()
{
	ll p=1;
	jc[0]=1;
	jc[1]=1;
	inv[0]=1;
	inv[1]=1;
	for(int i=2;i<MAXN;i++)
	{
		p=(p*i)%MOD;
		jc[i]=p;
	}
	for(int i=2;i<MAXN;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=2;i<MAXN;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}

void dfsroot(int x,int f)
{
    fa[x]=f;
    if(f) ins(rt[f],x);    //将x插入到f的二叉搜索树中,用于查询在f的孩子节点中小于某个值的孩子的数量
    for(int i=0;i<edge[x].size();i++)
    {
        if(edge[x][i]!=f)
        {
            dfsroot(edge[x][i],x);
            d[edge[x][i]]--;
        }
               //将d[i]变成i的孩子节点的个数
    }

}


int m,n;
int flag;
void dfs(int x)   //遍历B数组,x=B[m]
{
    if(m>n||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<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        edge[x].pb(y);
        edge[y].pb(x);
        d[x]++;
        d[y]++;
    }
    ans=0;
    res=1;
    for(int i=1;i<=n;i++)
        res=res*jc[d[i]-1]%MOD;
    for(int i=1;i<B[1];i++)
        ans=(ans+res*d[i]%MOD)%MOD;
    dfsroot(B[1],0);  //建立以B[1]为根的树
    res=res*d[B[1]]%MOD;   //res=以B[i]为根的树的所有方案数
    m=1,flag=0;
    dfs(B[1]);   //

    printf("%lld\n",ans);


    }
    return 0;
}

这里是pbds库版本pbds库

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
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<int> edge[MAXN];
tree<int,null_type,less<int>,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;i<MAXN;i++)
    {
        p=(p*i)%MOD;
        jc[i]=p;
    }
    for(int i=2;i<MAXN;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=2;i<MAXN;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}

void dfsroot(int x,int f)
{
    fa[x]=f;
    if(f) rt[f].insert(x);   //将x插入到f的二叉搜索树中,用于查询在f的孩子节点中小于某个值的孩子的数量
    for(int i=0;i<edge[x].size();i++)
    {
        if(edge[x][i]!=f)
        {
            dfsroot(edge[x][i],x);
            d[edge[x][i]]--;
        }
               //将d[i]变成i的孩子节点的个数
    }

}


int m,n;
int flag;
void dfs(int x)   //遍历B数组,x=B[m]
{
    if(m>n||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<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        edge[x].pb(y);
        edge[y].pb(x);
        d[x]++;
        d[y]++;
    }
    ans=0;
    res=1;
    for(int i=1;i<=n;i++)
        res=res*jc[d[i]-1]%MOD;
    for(int i=1;i<B[1];i++)
        ans=(ans+res*d[i]%MOD)%MOD;
    dfsroot(B[1],0);  //建立以B[1]为根的树
    res=res*d[B[1]]%MOD;   //res=以B[i]为根的树的所有方案数
    m=1,flag=0;
    dfs(B[1]);   //

    printf("%lld\n",ans);


    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值