给定一棵大小为n的有根树,点的标号为1,…,n。现在要把树转化成一个序列,要求一个点的后代在序列中先出现,求所有满足条件的序列的逆序对数之和。
n≤50
dp[i][j][k]
:
i
子树中
定义子树对应的序列为副序列,已合并的子树对应的序列为主序列。
主序列的长度为
每次把主序列和副序列合并得到新的主序列
首先两个序列内部的逆序对数之和随着合并后总方案数增多而增多
令
ans[i]
表示内部的逆序对数之和,
cas[i]
表示序列的方案数,
x
表示主序列,
方便起见,我们可以只枚举副序列中的元素
i
、其所在位置
令
sum[i][j]
表示主序列中,大小
≤i
,位置
≤j
的合法排列情况数总和
令
a
表示把副序列中的第k个数插入到主序列第j个数后面的方案数
令 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
欢迎提供新的变量名:)