HDU 5789 Permutation

给定一棵大小为n的有根树,点的标号为1,…,n。现在要把树转化成一个序列,要求一个点的后代在序列中先出现,求所有满足条件的序列的逆序对数之和。
n≤50

dp[i][j][k] : i 子树中j在第 k 个位置的合法排列情况数

定义子树对应的序列为副序列,已合并的子树对应的序列为主序列
主序列的长度为len,副序列长度为 len
每次把主序列和副序列合并得到新的主序列

首先两个序列内部的逆序对数之和随着合并后总方案数增多而增多
ans[i] 表示内部的逆序对数之和, cas[i] 表示序列的方案数, x 表示主序列,son表示副序列, x 表示新的主序列

ans[x]=Clenlen+len×(ans[x]×cas[son]+ans[son]×cas[x])

方便起见,我们可以只枚举副序列中的元素 i 、其所在位置k、插入到主序列中的第 j 个数后面,于是只要预处理前缀和O(1)得到主序列中,大小 i ,位置 >j 的合法排列情况数总和与大小 >i ,位置 j 的合法排列情况数总和即可。

sum[i][j] 表示主序列中,大小 i ,位置 j 的合法排列情况数总和

sum[i][j]=piqjdp[x][i][j]

a 表示把副序列中的第k个数插入到主序列第j个数后面的方案数

a=Cjk1+jClenjlenk+lenj

b 表示大小i,位置 >j 的合法排列情况数总和

b=sum[i][len]sum[i][j]

c 表示大小>i,位置 j 的合法排列情况数总和
c=sum[n][j]sum[i][j]

ans[x]+=dp[son][i][k]×a×(b+c)

计算贡献的复杂度为 O(n4)

合并也是类似的,不过不同的是在计算贡献的时候两边的序列相当于确定了,所以组合数乘一下就好了,合并的时候只是把一个序列安置到一个新序列中,所以还要乘上另一个序列总的方案数

枚举元素、原位置、新位置、共n次。
合并的复杂度为 O(n4)

每次合并之后重新预处理一下这个二维前缀和。
处理前缀和的复杂度为 O(n3)

所以我该怎么努力一下降到 O(n3) 呢==
是我的做法太朴素了先天不足吗==欢迎大家提供 O(n3) 的做法

树形dp

#include <set>
#include <ctime>
#include <queue>
#include <cstdio>
#include <bitset>
#include <cctype>
#include <bitset>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf (1<<30)
#define INF (1ll<<62)
#define fi first
#define se second
#define rep(x,s,t) for(register int x=s,t_=t;x<t_;++x)
#define per(x,s,t) for(register int x=t-1,s_=s;x>=s_;--x)
#define travel(x) for(int I=last[x],to;~I&&(to=e[I].to);I=e[I].nxt)
#define prt(x) cout<<#x<<":"<<x<<" "
#define prtn(x) cout<<#x<<":"<<x<<endl
#define pb(x) push_back(x)
#define hash asfmaljkg
#define rank asfjhgskjf
#define y1 asggnja
#define y2 slfvm
using namespace std;
typedef long long ll;
typedef pair<int,int> ii;
template<class T>void sc(T &x){
    int f=1;char c;x=0;
    while(c=getchar(),c<48)if(c=='-')f=-1;
    do x=x*10+(c^48);
    while(c=getchar(),c>47);
    x*=f;
}
template<class T>void nt(T x){
    if(!x)return;
    nt(x/10);
    putchar(x%10+'0');
}
template<class T>void pt(T x){
    if(x<0)putchar('-'),x=-x;
    if(!x)putchar('0');
    else nt(x);
}
template<class T>void ptn(T x){
    pt(x);putchar('\n');
}
template<class T>void pts(T x){
    pt(x);putchar(' ');
}
template<class T>inline void Max(T &x,T y){if(x<y)x=y;}
template<class T>inline void Min(T &x,T y){if(x>y)x=y;}

const int maxn=55;
const int mod=1e9+7;

int n,rt;
int last[maxn],ecnt;
struct Edge{
    int to,nxt;
}e[maxn<<1];
void ins(int u,int v){
    e[ecnt]=(Edge){v,last[u]};
    last[u]=ecnt++;
}

ll cnk[maxn][maxn];

ll ans[maxn],ss[maxn];
int sz[maxn];

int a[maxn],lx[maxn],rx[maxn],dfs_clock;//用来枚举子树元素 
void dfs(int x,int f){
    lx[x]=++dfs_clock;a[dfs_clock]=x;
    travel(x)if(to!=f)
        dfs(to,x);
    rx[x]=dfs_clock;
    sz[x]=rx[x]-lx[x]+1;
}

ll sum[maxn][maxn];
ll dp[maxn][maxn][maxn];
void deal(int x,int f){
    ans[x]=0;
    travel(x)if(to!=f)
        deal(to,x);

    int p=0;ss[x]=1;
    travel(x)if(to!=f){
        ans[x]=(ans[x]*cnk[p+sz[to]][sz[to]]%mod*ss[to]%mod
              +ans[to]*cnk[p+sz[to]][sz[to]]%mod*ss[x]%mod)%mod;//
        ans[x]%=mod;

        rep(i,lx[to],rx[to]+1){//枚举元素 
            rep(j,0,p+1){//枚举插入的位置
                rep(k,1,sz[to]+1){ 
                    ll a=cnk[k-1+j][j]*cnk[sz[to]-k+p-j][p-j]%mod;
                    ll b=(mod+sum[n][j]-sum[::a[i]][j])%mod;
                    ll c=(mod+sum[::a[i]][p]-sum[::a[i]][j])%mod;
                    ans[x]+=dp[to][::a[i]][k]*a%mod*(b+c)%mod;///
                    ans[x]%=mod;
                }
            }
        }
        rep(i,1,n+1)rep(j,1,n+1)sum[i][j]=0;

        int np=p+sz[to];

        rep(k,1,np+1){//枚举在新排列中的位置 
            rep(i,lx[x]+1,lx[x]+p+1){//枚举主排列的元素 
                rep(j,max(k-sz[to],1),min(k,p)+1){//枚举在主排列中的位置 
                    sum[a[i]][k]+=dp[x][a[i]][j]*cnk[k-1][j-1]%mod*cnk[np-k][p-j]%mod*ss[to]%mod;//ss
                    sum[a[i]][k]%=mod;
                }
            }
            rep(i,lx[to],rx[to]+1){//枚举副排列的元素
                rep(j,max(k-p,1),min(k,sz[to])+1){//枚举在副排列中的位置
                    sum[a[i]][k]+=dp[to][a[i]][j]*cnk[k-1][j-1]%mod*cnk[np-k][sz[to]-j]%mod*ss[x]%mod;
                    sum[a[i]][k]%=mod;
                }
            }
        }

        ss[x]=ss[x]*cnk[np][p]%mod*ss[to]%mod;
        p=np;

        rep(i,1,n+1)rep(j,1,n+1){
            dp[x][i][j]=sum[i][j];
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[x][i][j];
        }
    }

    dp[x][x][sz[x]]=ss[x];
    ans[x]+=sum[n][p]-sum[x][p];

    ans[x]%=mod;
}

void solve(){
    memset(last,-1,sizeof last);
    memset(dp,0,sizeof dp);
    memset(sum,0,sizeof sum);
    ecnt=0;

    rep(i,1,n){
        int u,v;
        sc(u);sc(v);
        ins(u,v);ins(v,u);
    }
    dfs_clock=0;dfs(rt,0);
    deal(rt,0);
    ptn(ans[rt]);
}

int main(){
//  freopen("pro.in","r",stdin);
//  freopen("chk.out","w",stdout);
//  int cas;sc(cas);
    rep(i,0,maxn){
        rep(j,1,i) cnk[i][j]=(cnk[i-1][j-1]+cnk[i-1][j])%mod;
        cnk[i][0]=cnk[i][i]=1;
    }
    while(~scanf("%d%d",&n,&rt))solve();
    return 0;
}

蒟蒻:
开始ans[]合并的不好,从开始写到AC中间隔了几道题,改掉原来错误的合并花了不少时间,封装起来应该会更好看一些,以后写题最好一次写一题(但昨天中途写的那题确实蛮有趣的==)

依然有笔误:ss写成了sz,x和to写混了
变量名应该写的好看一些是吧==以后表示方案数的变量就叫…cas[]?

诈尸灵魂OIer Tony整理的变量名:
mx to sum tmp num tar now cur top col flag data memo step rank host block delta factor rx ry res cnt pos val str vec tot dir mark tree perm from list spot Q/que mn/mi dp fa ans len dis nxt par cas org sub used left root comb heap edge prime matrix perm

欢迎提供新的变量名:)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值