P4099 [HEOI2013]SAO

传送门

n 个关卡有 n-1 个限制

所以这些限制构成一颗树

考虑树形DP

对一颗子树单独考虑

考虑有多少种顺序

设 f [ i ] 表示节点 i 的子树的总方案数

考虑儿子节点如何与父节点合并

发现父子之间有限制条件,所以 f 多加一维 f [ i ] [ j ] 表示节点 i 在子树中排第 j 时的方案数

子树合并时就可以看成两个序列合并

比如像这样(x和v是的父节点):

  { ,,x,, } + { 。。v 。。。}  =  { ,。。,v 。x  ,  。。, }

上图就是 f [ x ] [ 3 ] 与 f [ v ] [ 3 ] 的一种合并方案 --> f [ x ] [ 7 ]

然后考虑方案数的增长

儿子的一部分合并到父节点左边,另一部分合并到父节点右边

对于父节点 x 和儿子节点 v

我们枚举合并后的父节点的排名 k ,枚举合并前的父节点排名 j,枚举儿子分离的中间点 o

儿子左半部分合并到父亲的方案有 C[ k-1 ] [ k-j ]

右部分合并父亲的方案有 C[ sz[x] - k ] [ sz[x]-sz[v]-j ](sz[ x ]此时已经包括sz [ v ])

然后可以得到完整的转移方程(不考虑父子关系的情况):  

(sz是节点大小)

但这是O(n^3)的转移

优化十分显然,f [ v ] [ o ] 可以提出来用前缀和一起算,然后就是O(n^2)

然鹅我们还要考虑到父子间的限制...

那么如果 父节点要在子节点后   -->  k-j ≥ o ≥ 1

反之 sz[v] ≥ o > k-j

初始 f [ x ] [ 1 ] = 1 (合并前所有节点的子树只有它自己)

注意有多组数据,记得清空数组

实现看代码,要注意细节

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef unsigned long long ll;
inline int read()
{
    int x=0; char ch=getchar();
    while(ch<‘0‘||ch>‘9‘) ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x;
}
const int N=2e3+7,mo=1e9+7;
int fir[N],from[N<<1],to[N<<1],cnt;
inline void add(int &a,int &b)
{
    from[++cnt]=fir[a];
    fir[a]=cnt; to[cnt]=b;
}

inline ll fk(ll x) { return x>=mo ? x-mo : x; }//这样取模会快一点
int n;
bool mp[N][N];//存父子间的大小关系
ll C[N][N];//组合数
void pre()//预处理组合数
{
    C[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++) C[i][j]=fk(C[i-1][j-1]+C[i-1][j]);
}
int sz[N];
ll f[N][N],g[N][N];//g是f的前缀和
void dfs(int x,int fa)
{
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(v==fa) continue;
        dfs(v,x); sz[x]+=sz[v];
        for(int j=sz[x];j;j--)//注意j从大到小转移,先更新大的再更新小的
        {
            int L=min(sz[x]-sz[v],j); ll sum=0;
            for(int k=1;k<=L;k++)
            {
                if(mp[x][v])//判断大小关系
                {
                    int l=j-k,r=sz[v];//o的范围
                    if(l<r)//注意边界
                    {
                        ll t=( C[j-1][j-k] * C[sz[x]-j][sz[x]-sz[v]-k] )%mo;
                        sum=fk(sum+( (f[x][k]*t)%mo * fk(g[v][r]+mo-g[v][l]) )%mo);
                    }
                }
                else
                {
                    int r=min(j-k,sz[v]); ll t=( C[j-1][j-k] * C[sz[x]-j][sz[x]-sz[v]-k] )%mo;
                    sum=fk(sum+( (f[x][k]*t)%mo * g[v][r] )%mo);
                }
            }
            f[x][j]=sum;
        }
    }
    for(int i=1;i<=sz[x];i++) g[x][i]=fk(g[x][i-1]+f[x][i]);//计算前缀和
}
inline void clr()
{
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=g[i][j]=mp[i][j]=0;
    for(int i=1;i<=n;i++) sz[i]=f[i][1]=1/*注意*/,fir[i]=0;
    cnt=0;//cnt别忘了清空
}
int T;
int main()
{
    T=read();
    while(T--)
    {
        int a,b; char c[5];
        n=read(); pre(); clr();
        for(int i=1;i<n;i++)
        {
            a=read(); scanf("%s",c); b=read();
            a++; b++;
            add(a,b); add(b,a);
            if(c[0]==‘<‘) mp[a][b]=1;
            else mp[b][a]=1;
        }
        dfs(1,1);
        ll ans=0;
        for(int i=1;i<=n;i++) ans=fk(ans+f[1][i]);
        printf("%lld\n",ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/LLTYYC/p/9809720.html

时间: 2024-10-04 12:10:00

P4099 [HEOI2013]SAO的相关文章

【BZOJ3167/4824】[Heoi2013]Sao/[Cqoi2017]老C的键盘

[BZOJ3167][Heoi2013]Sao Description WelcometoSAO(StrangeandAbnormalOnline).这是一个VRMMORPG,含有n个关卡.但是,挑战不同关卡的顺序是一个很大的问题.有n–1个对于挑战关卡的限制,诸如第i个关卡必须在第j个关卡前挑战,或者完成了第k个关卡才能挑战第l个关卡.并且,如果不考虑限制的方向性,那么在这n–1个限制的情况下,任何两个关卡都存在某种程度的关联性.即,我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集

[HEOI2013]SAO

[HEOI2013]SAO 这道题是个不错的计数题,考察了调换求和顺序再前缀和优化,难点在状态设计,比较考察思维. 一句话题意:给你一棵数,树边为有向边,求其拓扑序数. 对DAG求拓扑数是一个NP问题,但是这里保证是一棵树,所以我们可以用树形DP来求解. 状态的设计上,光设结点编号\(u\)不够,还需要设计一维\(i\)表示结点\(u\)在以\(u\)为根的子树中的拓扑序的第\(i\)位,这样我们就可以写转移方程了. 对于\(u \rightarrow v\) \[ F'[u][k] = \Si

[HEOI2013]SAO ——计数问题

题目大意: Welcome to SAO ( Strange and Abnormal Online).这是一个 VR MMORPG, 含有 n 个关卡.但是,挑战不同关卡的顺序是一个很大的问题. 有 n – 1 个对于挑战关卡的限制,诸如第 i 个关卡必须在第 j 个关卡前挑战, 或者完成了第 k 个关卡才能挑战第 l 个关卡.并且,如果不考虑限制的方向性, 那么在这 n – 1 个限制的情况下,任何两个关卡都存在某种程度的关联性.即, 我们不能把所有关卡分成两个非空且不相交的子集,使得这两个

BZOJ 3167: [Heoi2013]Sao

Description 一个排列,满足一些限制,形成一个树形结构,求方案数\(T\leqslant 5,n\leqslant 1\times 10^3\) Solution 树形DP. \(f[i][j]\)表示\(i\)是在他的子树中排名为\(j\). 也是暴力合并信息,复杂度分析同上题. Code /************************************************************** Problem: 3167 User: BeiYu Language

bzoj3167 [Heoi2013]Sao

传送门 这题神坑啊--明明是你菜 首先大家都知道原题等价于给每个点分配一个$1$~$n$且两两不同的权值,同时还需要满足一些大于/小于关系的方案数. 先看一眼数据范围,既然写明了$n\le 1000$,那就应该是什么$O(n^2)$的做法了.显然这个东西只能是个DP,考虑到题中给出的是一个树形结构,那么就可以利用子树的相对独立性进行DP:设$f_{i,j}$表示以$i$为根的子树中有$j$个点的权值大于$i$的权值时的方案数,显然最终答案就是$\sum_{i}f_{root,i}$. 然后考虑怎

[BZOJ4824][CQOI2017]老C的键盘(树形DP)

4824: [Cqoi2017]老C的键盘 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 193  Solved: 149[Submit][Status][Discuss] Description 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序 在某种神奇力量的驱使之下跑得非常快.小 Q 也是一个程序员.有一天他悄悄潜入了老 C 的家中,想要看看这个 键盘究竟有何妙

DP常用优化

DP常用优化 一.前缀和优化 当遇到类似:\(f[i] = \sum_{j = k}^{i} g[j]\)的转移时,可以通过预处理出\(g[i]\)的前缀和\(s[i]\),将\(O(n)\)的求和转换为\(O(1)?\)的操作. [HAOI2009]逆序对数列 [HAOI2008]木棍分割 二分答案+dp P4099 [HEOI2013]SAO 树形dp 二.决策单调性--单调队列优化 接下来几种优化方法主要是对1d/1d dp的优化,其中xd/yd dp指的是状态数有\(n^x\)种,每个状

[提升性选讲] 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)

转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7337179.html 树形DP是一种在树上进行的DP相对比较难的DP题型.由于状态的定义多种多样,因此解法也五花八门,经常成为高水平考试的考点之一. 在树形DP的问题中,有这样一类问题:其数据范围相对较小,并且状态转移一般与两两节点之间的某些关系有关. 今天,我们就来研究一下这类型的问题,并且总结一种(相对套路的)解决大多数类型题的思路. 首先,我们用一道相对简单的例题来初步了解这个类型题的大致思路,以及一

【BZOJ】【3166】【HEOI2013】Alo

可持久化Trie+set Orz zyf…… 搞区间中次大值不好搞,那么我们就反过来,找一个数,然后看它在哪些区间里是次大值…… (然而事实上我们并不用真的把这个区间具体是什么找见,只要知道它可以跟哪一段数搞Xor就可以了! 而这个区间就是……左边第二个比他大的数的位置+1 ~ 右边第二个比它大的数的位置-1 这中间所有数都可以跟它搞Xor= =,我们总能找到一个相应的区间…… (我一开始理解成,这个区间就是我们要找的,a[i]为次大数的区间,然而这不是左边有一个比它大的,右边也有一个比它大的吗