题目大意:

Welcome to SAO ( Strange and Abnormal Online)。这是一个 VR MMORPG, 含有 n 个关卡。但是,挑战不同关卡的顺序是一个很大的问题。

有 n – 1 个对于挑战关卡的限制,诸如第 i 个关卡必须在第 j 个关卡前挑战, 或者完成了第 k 个关卡才能挑战第 l 个关卡。并且,如果不考虑限制的方向性, 那么在这 n – 1 个限制的情况下,任何两个关卡都存在某种程度的关联性。即, 我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集之间没有任 何限制。

对于每个数据,输出一行一个整数,为攻克关卡的顺序方案个数,mod 1,000,000,007 输出。

题目翻译:

发现最后一句话就是说:这是一棵树形图。

所以我们现在有了一棵树,只是边是有向边,挑战的限制就是边的方向,我们必须把所有指向x0的关卡全部通过,才能通过x0关卡。

其实,所有指向x0的边就是它的度数,所以可以看出来,

这个题是让我们求这个树形图有多少种拓扑序。

分析:

这个题即使看了题解也是理解了半天。网上题解也不是很多,做法类似。

树形计数问题,可以用树形DP,首先我们可以先尝试定义一维,定义f[i]表示以i为根的子树的拓扑序有多少种,现在我们需要考虑怎样将若干个儿子的值转移到父亲上。

发现,如果把两个儿子的拓扑序,看做是两个区间,那么我们做的其实是一个区间合并的操作。

但是由于边其实是有向的,(虽然我们是无向边建树)实际边的方向还决定父亲,该儿子的真正完全的拓扑序谁在前,谁在后。就是说,要先过了父亲,还是先过了儿子。

非常无从下手的感觉。我们需要再定义一维。

于是我们这样定义:

f[i][j]表示,在以i为根的子树中,根节点i排在第j位的拓扑序的种类数。(其实所有的拓扑序就是f[i][1-size])

可以发现,j不同时,方案数一定是独立的。

现在我们要考虑转移:

当我们循环到x的一个儿子y的时候,size[x]记录的是当前x与其前面所有儿子子树的size和,就是还没有包括y

那么前面说了,就是一个区间合并,我们以x的位置作为断点考虑合并。

先分类(因为我们无向边建树,但是实际上是有向边。)

①x<y 即先通过x,再通过y。

这个时候,拓扑序合并后x的排名一定在y的前面。

对于f[x][k],最终x前面有k-1个元素。可以从f[x][i](1<=i<=min(k,size))和 f[y][j](j的范围随后再确定)转移过来。转移之后,区间内共有size[x]+size[y]个数

就是说,我在合并后的拓扑序中,先从之前的f[x][i]中的方案数中拿出若干种,放进大区间里,再从f[y][j]里选择一些方案数,放进大区间里。所以这里i一定小于等于k

前k-1个位置,从之前的数中先挑出i-1个位置,有C(k-1,i-1)种选法,

后size[x]+size[y]-k个位置(不算x), 已经选择了i-1个数,还剩下size[x]-i个数(x自己不算),有C(size[x]+size[y]-k,size[x]-i)种选法。

再乘上每个选上的集合中自己的变化,也就是f[x][i]自己本身(类似多重集合的排列)

剩下的位置就是f[y][j]的了,不需要再乘组合数,只需乘上f[y][j]就好。

现在我们要确定j的取值范围:

对于x<y的情况,x之前的数,我们已经填了i-1个位置,还剩下k-i个位置要填,

为了使得y在x的后面,而y之前还能放j-1个数,所以要使得:j-1>=k-i,当然j<=size[y]

所以,j的循环范围是,k-i+1<=j<=size[y]

所以,对于x<y的情况,我们可以列出状态转移的方程是:

f[x][k]=(1<=i<=min(k,size[x]))(k-i+1<=j<=size[y]) f[x][i]*c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i]*f[y][j]

这样子,发现每次要循环一遍j,复杂度是O(n^4)的,直接挂掉。。。

又发现,对于同一个y,我们好像加的是同一些树,循环的是同一些j。。。

我们把这个式子用乘法分配律提出来一下:

f[x][k]=(1<=i<=min(k,size[x])) f[x][i]*c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i]*(f[y][k-i+1]+...f[y][size[y]])

所以,加粗部分是可以通过一个前缀合优化处理的,复杂度变成O(1)。

①x>y 即先通过y,再通过x。

其实是同理的。f[x][i](1<=i<=min(k,size)),i的范围没有变。

但是由于要保证y在x的前面,j-1个元素,必然不能填满k-i个位置

所以,j-1<k-i (注意是小于,不是小于等于,因为还有一个位置是y自己,所以要用j-1个位置填不满k-i个位置)并且j>=1

所以这里的状态转移方程是:

f[x][k]=(1<=i<=min(k,size[x]))(1<=j<=k-i) f[x][i]*c[k-1][i-1]*c[size[x]+size[y]-k][size[x]-i]*f[y][j]

同理可以乘法分配律,前缀和优化。

详见代码:

#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
using namespace std;
+;
;
int n,t;
struct node{
    int nxt,to,val;
}bian[*N];
int head[N],cnt;
void add(int x,int y,int z)
{
    bian[++cnt].to=y;
    bian[cnt].nxt=head[x];
    bian[cnt].val=z;
    head[x]=cnt;
}

ull f[N][N],sumdp[N][N];
ull c[N][N];
int size[N];
bool vis[N];
void dfs(int x)
{
    size[x]=;
    f[x][]=;
    vis[x]=;
    for(int o=head[x];o;o=bian[o].nxt)
    {
        int y=bian[o].to;
        if(!vis[y])
        {
        dfs(y);
        if(bian[o].val)//......x...y
        {
            ;k--)
            {
                ull sum=;
                ;i<=min(size[x],k);i++)
                {
                    int l=k-i,r=size[y];
                    ull del=(sumdp[y][size[y]]+mod-sumdp[y][k-i])%mod;//前缀和差值
                    if(l<r)
                    {
                    ull q=(f[x][i]*del)%mod,p=(c[k-][i-]*c[size[x]+size[y]-k][size[x]-i])%mod;
                    p*=q;p=p%mod;sum+=p;sum%=mod;//这里,必须四个数分别计算并取模,否则会爆long long
                    }
                }
                f[x][k]=sum;
            }
        }
        else//.........y...x
        {
            ;k--)
            {
                ull sum=;
                ;i<=min(size[x],k-);i++)
                {
                    int r=min(size[y],k-i);
                    ull del=sumdp[y][r];
                    ull q=(f[x][i]*del)%mod,p=(c[k-][i-]*c[size[x]+size[y]-k][size[x]-i])%mod;
                    p*=q;p=p%mod;sum+=p;sum%=mod;
                }
                f[x][k]=sum;
            }
        }
        size[x]+=size[y];
        }
    }
    ;i<=size[x];i++)//处理完了x,赋值前缀和,以便后续使用
        sumdp[x][i]=(sumdp[x][i-]+f[x][i])%mod;
}

void clear()//清空
{
    cnt=;
    ;i<=n;i++)
    {
        head[i]=;vis[i]=;
        size[i]=;
        ;j<=n;j++)
         sumdp[i][j]=,f[i][j]=;
    }
}
int main()
{
    c[][]=;
    ;i<=;i++)
    {
        c[i][]=;
        ;j<=i;j++)
         c[i][j]=(c[i-][j]+c[i-][j-])%mod;
    }//1000的范围,组合数打表
    cin>>t;
    while(t)
    {
        scanf("%d",&n);
        clear();
        int x,y;
        ];
        ;i<=n-;i++)
        {
            scanf("%d%s%d",&x,q,&y);
            x++,y++;//变成以1开始
            ]=='<')
            {
                add(x,y,);
                add(y,x,);
            }
            else{
                add(y,x,);
                add(x,y,);
            }//建无向边,x,y距离是1,表示x<y 先过x后过 y
        }
        dfs();
        ull ans=;
        ;i<=size[];i++)
        {
            ans=(ans+f[][i])%mod;
        }//方案数
        printf("%llu\n",ans);
        t--;
    }
    ;
}

基本思路和代码参考shadowice1984,https://www.luogu.org/blog/ShadowassIIXVIIIIV/solution-p4099

详细化了很多。

[HEOI2013]SAO ——计数问题的更多相关文章

  1. 3167: [Heoi2013]Sao [树形DP]

    3167: [Heoi2013]Sao 题意: n个点的"有向"树,求拓扑排序方案数 Welcome to Sword Art Online!!! 一开始想错了...没有考虑一个点 ...

  2. 【BZOJ3167】[HEOI2013]SAO(动态规划)

    [BZOJ3167][HEOI2013]SAO(动态规划) 题面 BZOJ 洛谷 题解 显然限制条件是一个\(DAG\)(不考虑边的方向的话就是一棵树了). 那么考虑树型\(dp\),设\(f[i][ ...

  3. P4099 [HEOI2013]SAO

    P4099 [HEOI2013]SAO 贼板子有意思的一个题---我()竟然没看题解 有一张连成树的有向图,球拓扑序数量. 树形dp,设\(f[i][j]\)表示\(i\)在子树中\(i\)拓扑序上排 ...

  4. BZOJ 3167: [Heoi2013]Sao

    3167: [Heoi2013]Sao Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 96  Solved: 36[Submit][Status][D ...

  5. P4099 [HEOI2013]SAO(树形dp)

    P4099 [HEOI2013]SAO 我们设$f[u][k]$表示以拓扑序编号为$k$的点$u$,以$u$为根的子树中的元素所组成的序列方案数 蓝后我们在找一个以$v$为根的子树. 我们的任务就是在 ...

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

    [BZOJ3167][Heoi2013]Sao Description WelcometoSAO(StrangeandAbnormalOnline).这是一个VRMMORPG,含有n个关卡.但是,挑战 ...

  7. [BZOJ3167][P4099][HEOI2013]SAO(树形DP)

    题目描述 Welcome to SAO ( Strange and Abnormal Online).这是一个 VR MMORPG, 含有 n 个关卡.但是,挑战不同关卡的顺序是一个很大的问题. 有 ...

  8. luogu P4099 [HEOI2013]SAO

    传送门 吐槽题目标题 这个依赖关系是个树,可以考虑树型dp,设f_i表示子树i的答案 因为这是个序列问题,是要考虑某个数的位置的,所以设\(f_{i,j}\)表示子树i构成的序列,i在第j个位置的方案 ...

  9. Luogu4099 HEOI2013 SAO 组合、树形DP

    传送门 值得注意的是一般的DAG的拓扑序列数量是NP问题,所以不能直接入手 题目中给出的图可以看做是一个树形图,虽然方向比较迷.考虑使用树形图的性质 不妨任选一个点为根做树形DP,注意到数的位置与方案 ...

随机推荐

  1. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  2. javascript详解系列-函数表达式

    1.递归 function fact(num){ if(num<1){ return 1; } else{ return num*fact(num-1); } } var author = fa ...

  3. C# 根据正则表达式来判断输入的是不是数字

    最近在做输入判断的时候出现了一个需要判断输入合法性的问题,就是判断输入的是不是数字,判断方法是根据正则表达式来判断,具体方法如下: private bool IsRightNum(string str ...

  4. 。。。无语的Eclipse+Tomact。。。

    晕哦,今天又被Eclipse给骗了,今天部署了一个SSH的环境,搞了半天,JAR包是通过BuildPath导入进去的,怎么搞都报错,说是找不到Spring-Web的一个Jar包,差点没有把我给弄死.. ...

  5. BMP文件格式分析

    前两天要做一个读取bmp文件的小程序,顺便查找了一些关于BMP格式的文章,现在post上来. 简介 BMP(Bitmap-File)图形文件是Windows采用的图形文件格式,在Windows环境下运 ...

  6. 简单3d RPG游戏 之 002 生命条(二)

    在游戏中,游戏人物的血条可能会因为受伤或吃血瓶而长度变化,所以需要将血条的长度单独提出来作为一个变量,方便直接修改数值. public float healthBarLength; 改变生命值函数如下 ...

  7. Randomize select algorithm 随机选择算法

    从一个序列里面选择第k大的数在没有学习算法导论之前我想最通用的想法是给这个数组排序,然后按照排序结果返回第k大的数值.如果使用排序方法来做的话时间复杂度肯定至少为O(nlgn). 问题是从序列中选择第 ...

  8. Cordova+angularjs+ionic+vs2015开发(二)

    欢迎加群学习:457351423 这里有4000多部学习视频,涵盖各种技术,有需要的欢迎进群学习! 一.创建空白Cordova应用 打开VS,选择[新建项目],选择其它语言JavaScript或者Ty ...

  9. 动画讲解 Eclipse 常用快捷键

    Eclipse有强大的编辑功能, 工欲善其事,必先利其器, 掌握Eclipse快捷键,可以大大提高工作效率. 小坦克我花了一整天时间, 精选了一些常用的快捷键操作,并且精心录制了动画, 让你一看就会. ...

  10. X Shell 4配色方案[Solarized Dark]

    X Shell 4是个很好的Windows下登录Linux服务器的终端,比Putty好用 X Shell 4的下面这种方案,我个人很喜欢 用vim写shell脚本的效果: 按如下步骤配置: 1)把下面 ...