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
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序方案数是,v是树x的全部节点(包括x)
然后我们遍历以不同点为根的树,发现是公共的部分,就是树种全部节点的度数-1的阶乘再乘再一起
那么我们就可以发现以任意一个节点为根的树的dfs序方案数就是根的度数*树中所有节点的度数-1阶乘的累乘
那么我们就可以算以x为dfs序的开头的方案数 ,即以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,对答案的贡献为
算完第三位小于B[3]的贡献后,我们就要往5走,因为这里我们是遍历B逐位确定答案的,所以一定是按照B的顺序走
这里就分为两种情况了。
1.5是2的孩子节点,那么我们就自然地往5走
2.如果5不是2的孩子节点,并且2孩子节点的数量>0,那么我们其实就不用在遍历下去了,因为答案都已经算过了。
因为dfs序中2后面的那一位永远都不可能是5。
如果2的孩子节点都小于5,因为是按照dfs序进行的,所以2后面的一定可以任意走了,即2后面的那些位都是任意的
如果2的孩子节点都大于5, 那么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是真的.......
//
// 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;
}