XJOI 旅行(树形DP)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了XJOI 旅行(树形DP)相关的知识,希望对你有一定的参考价值。

技术分享

题意非常清真,就是问你一棵无根树的所有可能的中dfs序中,有多少个字典序严格小于给定的一个排列.

但是也非常难写.

先来分类讨论一波:

第一部分:

如果树根小于排列的第一个数,那么所有可能的dfs序都会加到答案中去

那么统计一下每个节点的度数,

设f(x)表示以x为根节点的dfs序种类数,

f(x)=fac[du[x]]*fac[du[1]-1]*fac[du[2]-1].......(注意,后面不乘fac[du[x]-1])

其中du[x]表示x节点的度,也就是连的无向边的数量

fac[x]表示x!

先处理出f[1]

int now=fac[du[1]];  for (int i=2; i<=n; i++) now=1ll*now*fac[du[i]-1]%M;

然后,就可以O(n)的求出所有f值,加到ans上

for (int i=1; i<bb[1]; i++){
    ans=(ans+now)%M;
    now=1ll*now*fp(du[i],M-2)%M*du[i+1]%M;

}

fp是快速幂的意思,M是取模的数,fp(du[i],M-2)就是求du[i]的逆元.

然后第一部分就完成了

第二部分:

如果根节点是排列的第一个数,

那么就分两部分统计答案,

一是现在的点的儿子中有多少个没有遍历过又比下一个排列小的

二是现在的点的儿子如果有一个就是下一个排列,就统计子树中的方案数乘以除了这棵子树之外任意乱走的方案数.

具体实现细节很多非常麻烦,例如一棵子树如果没有走完就跳出就不再统计答案.

然后为了防止被卡菊花图还要写一个数据结构.

这是不带数据结构的:

技术分享
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int M=1e9+7,N=3e5+10;
 
int fi[N],ne[N*2],b[N*2],bb[N],visit[N],tfa[N];
int k,n,ans,u,v,ind;
bool flag=1;
void add(int x,int y){
    b[++k]=y; ne[k]=fi[x]; fi[x]=k;
}
  
inline int fp(int x,int y){
    int res=1;
    for (; y; y>>=1,x=1ll*x*x%M) if (y&1) res=1ll*res*x%M;
    return res;
}
  
int fac[N];
inline void prew(){
    fac[0]=1; for (int i=1; i<=n; i++) fac[i]=1ll*fac[i-1]*i%M;//if factorial 0=1
}
  
int re[N],son[N];
int calc(int x,int fa){
    re[x]=1ll*re[x]*(son[x]+1)%M*re[fa]%M*fp(re[x],M-2)%M*fp(son[fa],M-2)%M;
    son[x]++;
    return re[x];
}
void first(int x,int fa,const int ind){
    re[x]=1; son[x]=0;
    for (int j=fi[x]; j; j=ne[j])
    if (b[j]!=fa){
        first(b[j],x,ind);
        son[x]++;
        re[x]=1ll*re[x]*re[b[j]]%M;
        if (ind) tfa[b[j]]=x;
    }
    re[x]=1ll*re[x]*fac[son[x]]%M;
}
void dfs(int x,int fa){
    for (int j=fi[x]; j; j=ne[j])
    if (b[j]!=fa){
        int re0=re[x],son0=son[x];//original re[x]
        if (b[j]<bb[1]) ans=(ans+calc(b[j],x))%M; else calc(b[j],x);
        dfs(b[j],x);
        re[x]=re0;
        son[x]=son0;
    }
}
int change(int x,int fa){
    visit[x]=1; ++ind; --son[fa];
    int res=0,t=1ll*re[x]*fp(son[x],M-2)%M;
    while (1){
        int numlittle=0;
        for (int j=fi[x]; j; j=ne[j]) if (b[j]<bb[ind+1]&&!visit[b[j]]) ++numlittle; res=(res+1ll*numlittle*t%M)%M;
        if (tfa[bb[ind+1]]==x){
            t=1ll*t*fp(re[bb[ind+1]],M-2)%M;
            res=(res+1ll*t*change(bb[ind+1],x)%M)%M;
            if (!flag) return res;
            t=1ll*t*fp(son[x],M-2)%M;
        }
        else{
            if (son[x]) flag=0;
            break;
        }
    }
    return res;
}
int main(){
    scanf("%d",&n); prew();
    for (int i=1; i<=n; i++) scanf("%d",&bb[i]);
    for (int i=1; i<n; i++){
        scanf("%d%d",&u,&v);
        add(u,v); add(v,u);
    }
    first(1,0,0); if (1<bb[1]) ans=re[1];
    dfs(1,0); first(bb[1],0,1);
    ans=(ans+change(bb[1],0))%M;
    printf("%d\\n",ans);
}
my_past

这是加了zkw的:

 

技术分享
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int M=1e9+7,N=3e5+10;

int fi[N],ne[N*2],b[N*2],bb[N],visit[N],tfa[N],op[N],du[N];
int k,n,ans,u,v,ind;
bool flag=1;
vector<int>g[N];

namespace zkw{
    vector<int>st[N];
    int u[N];
    inline void update(int x,int y,int z){
        for (int i=u[x]+y; i; i>>=1) st[x][i]+=z;
    }
    int ask(int x,int s,int t){
        if (t>=st[x].size()) t=st[x].size()-1;
        int res=0;
        for (s+=u[x]-1,t+=u[x]+1; s^t^1; s>>=1,t>>=1){
            if (~s&1) res+=st[x][s^1];
            if (t&1) res+=st[x][t^1];
        }
        return res;
    }
    void prew(int x){
        for (u[x]=1; u[x]<g[x].size(); u[x]<<=1); u[x]--;
        for (int i=1; i<=u[x]+g[x].size()+1; i++) st[x].push_back(0);
        for (vector<int>::iterator it=g[x].begin(); it!=g[x].end(); it++) update(x,op[*it],1);
    }
}

void add(int x,int y){
    b[++k]=y; ne[k]=fi[x]; fi[x]=k;
}
  
inline int fp(int x,int y){
    int res=1;
    for (; y; y>>=1,x=1ll*x*x%M) if (y&1) res=1ll*res*x%M;
    return res;
}
  
int fac[N];
inline void prew(){
    fac[0]=1; for (int i=1; i<=n; i++) fac[i]=1ll*fac[i-1]*i%M;//if factorial 0=1
}
  
int re[N],son[N];
void first(int x,int fa){
    re[x]=1;
    for (int j=fi[x]; j; j=ne[j])
    if (b[j]!=fa){
        first(b[j],x);
        son[x]++;
        re[x]=1ll*re[x]*re[b[j]]%M;
        g[x].push_back(b[j]);
        tfa[b[j]]=x;
    }
    re[x]=1ll*re[x]*fac[son[x]]%M;
}
int change(int x,int fa){
    if (fa) zkw::update(fa,op[x],-1); ++ind; --son[fa];
    int res=0,t=1ll*re[x]*fp(son[x],M-2)%M;
    while (1){
        int numlittle=0;
        if (!g[x].empty()){
            vector<int>::iterator it=lower_bound(g[x].begin(),g[x].end(),bb[ind+1]);
            numlittle=zkw::ask(x,1,it-g[x].begin());
        }
           res=(res+1ll*numlittle*t%M)%M;
        if (tfa[bb[ind+1]]==x){
            t=1ll*t*fp(re[bb[ind+1]],M-2)%M;
            res=(res+1ll*t*change(bb[ind+1],x)%M)%M;
            if (!flag) return res;
            t=1ll*t*fp(son[x],M-2)%M;
        }
        else{
            if (son[x]) flag=0;
            break;
        }
    }
    return res;
}
int main(){
    scanf("%d",&n); prew();
    for (int i=1; i<=n; i++) scanf("%d",&bb[i]);
    for (int i=1; i<n; i++){
        scanf("%d%d",&u,&v);
        ++du[u]; ++du[v];
        add(u,v); add(v,u);
    }
       int now=fac[du[1]]; for (int i=2; i<=n; i++) now=1ll*now*fac[du[i]-1]%M;
       for (int i=1; i<bb[1]; i++){
           ans=(ans+now)%M;
        now=1ll*now*fp(du[i],M-2)%M*du[i+1]%M;
    }
    first(bb[1],0);
    for (int i=1; i<=n; i++){
        sort(g[i].begin(),g[i].end());
        int ttt=0;
        for (vector<int>::iterator it=g[i].begin(); it!=g[i].end(); it++) op[*it]=++ttt;
        zkw::prew(i);
    }
    ans=(ans+change(bb[1],0))%M;
    printf("%d\\n",ans);
}
my

 

以上是关于XJOI 旅行(树形DP)的主要内容,如果未能解决你的问题,请参考以下文章

XJOI xtx的旅行(状压+spfa)

XJOI Invoker(DP,数学,高精度)(未完待续)

XJOI 传送(线性筛数论函数,dp)

XJOI 并行程序(概率DP)

XJOI3354题解

三中校内训练旅行