[HEOI2013]SAO ——计数问题
题目大意:
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;
const int N=+;
const int mod=1e9+;
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
{
for(int k=size[x]+size[y];k>=;k--)
{
ull sum=;
for(int i=;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
{
for(int k=size[x]+size[y];k>=;k--)
{
ull sum=;
for(int i=;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];
}
}
for(int i=;i<=size[x];i++)//处理完了x,赋值前缀和,以便后续使用
sumdp[x][i]=(sumdp[x][i-]+f[x][i])%mod;
} void clear()//清空
{
cnt=;
for(int i=;i<=n;i++)
{
head[i]=;vis[i]=;
size[i]=;
for(int j=;j<=n;j++)
sumdp[i][j]=,f[i][j]=;
}
}
int main()
{
c[][]=;
for(int i=;i<=;i++)
{
c[i][]=;
for(int j=;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;
char q[];
for(int i=;i<=n-;i++)
{
scanf("%d%s%d",&x,q,&y);
x++,y++;//变成以1开始
if(q[]=='<')
{
add(x,y,);
add(y,x,);
}
else{
add(y,x,);
add(x,y,);
}//建无向边,x,y距离是1,表示x<y 先过x后过 y
}
dfs();
ull ans=;
for(int i=;i<=size[];i++)
{
ans=(ans+f[][i])%mod;
}//方案数
printf("%llu\n",ans);
t--;
}
return ;
}
基本思路和代码参考shadowice1984,https://www.luogu.org/blog/ShadowassIIXVIIIIV/solution-p4099
详细化了很多。
[HEOI2013]SAO ——计数问题的更多相关文章
- 3167: [Heoi2013]Sao [树形DP]
3167: [Heoi2013]Sao 题意: n个点的"有向"树,求拓扑排序方案数 Welcome to Sword Art Online!!! 一开始想错了...没有考虑一个点 ...
- 【BZOJ3167】[HEOI2013]SAO(动态规划)
[BZOJ3167][HEOI2013]SAO(动态规划) 题面 BZOJ 洛谷 题解 显然限制条件是一个\(DAG\)(不考虑边的方向的话就是一棵树了). 那么考虑树型\(dp\),设\(f[i][ ...
- P4099 [HEOI2013]SAO
P4099 [HEOI2013]SAO 贼板子有意思的一个题---我()竟然没看题解 有一张连成树的有向图,球拓扑序数量. 树形dp,设\(f[i][j]\)表示\(i\)在子树中\(i\)拓扑序上排 ...
- BZOJ 3167: [Heoi2013]Sao
3167: [Heoi2013]Sao Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 96 Solved: 36[Submit][Status][D ...
- P4099 [HEOI2013]SAO(树形dp)
P4099 [HEOI2013]SAO 我们设$f[u][k]$表示以拓扑序编号为$k$的点$u$,以$u$为根的子树中的元素所组成的序列方案数 蓝后我们在找一个以$v$为根的子树. 我们的任务就是在 ...
- 【BZOJ3167/4824】[Heoi2013]Sao/[Cqoi2017]老C的键盘
[BZOJ3167][Heoi2013]Sao Description WelcometoSAO(StrangeandAbnormalOnline).这是一个VRMMORPG,含有n个关卡.但是,挑战 ...
- [HEOI2013]SAO(树上dp,计数)
[HEOI2013]SAO (这写了一个晚上QAQ,可能是我太蠢了吧.) 题目说只有\(n-1\)条边,然而每个点又相互联系.说明它的结构是一个类似树的结构,但是是有向边连接的,题目问的是方案个数,那 ...
- 【做题记录】 [HEOI2013]SAO
P4099 [HEOI2013]SAO 类型:树形 \(\text{DP}\) 这里主要补充一下 \(O(n^3)\) 的 \(\text{DP}\) 优化的过程,基础转移方程推导可以参考其他巨佬的博 ...
- [BZOJ3167][P4099][HEOI2013]SAO(树形DP)
题目描述 Welcome to SAO ( Strange and Abnormal Online).这是一个 VR MMORPG, 含有 n 个关卡.但是,挑战不同关卡的顺序是一个很大的问题. 有 ...
随机推荐
- Python:线程之定位与销毁
背景 开工前我就觉得有什么不太对劲,感觉要背锅.这可不,上班第三天就捅锅了. 我们有个了不起的后台程序,可以动态加载模块,并以线程方式运行,通过这种形式实现插件的功能.而模块更新时候,后台程序自身不会 ...
- .NETCore_初探
1. Core默认的服务注册 Statup文件(rogram类型中创建 WebHost时使用的)中有俩个方法: Configure和ConfigureServices(将服务放置到容器里面) publ ...
- Terraform:简介
在 DevOps 实践中,基础设施即代码如何落地是一个绕不开的话题.像 Chef,Puppet 等成熟的配置管理工具,都能够满足一定程度的需求,但有没有更友好的工具能够满足我们绝大多数的需求?笔者认为 ...
- Ionic 入门与实战之第一章:Ionic 介绍与相关学习资源
原文发表于我的技术博客 本文是「Ionic 入门与实战」系列连载的第一章,主要对 Ionic 的概念.发展历程.适配的移动平台等知识进行了介绍,并分享了 Ionic 相关的学习资源. 原文发表于我的技 ...
- LVM基础详细说明及动态扩容lvm逻辑卷的操作记录
LVM概念:---------------------------------------------------------------------------------------------- ...
- 第二次作业 对VC++6.0编译软件的评价
首先这个软件伴随着我们很长时间了,它是我们一上大学最先接触的,也是应用相当多的一个软件,其实在最初的时候,我对编译软件的理解非常有限,觉得它能实现一个代码的功能十分神奇的一件事情,虽然彼时我们写的代码 ...
- 安装Visual Studio开发平台
1.找一个VS2013的安装包,下载到D盘上,勾选相应的选项安装. 安装的过程很漫长,至少需要一个小时. 2.安装已完成,启动. . 3.登录. \ 4启动VS2013. 5.新建c#类库 6.输入代 ...
- linux第一次读书笔记
第一章 LINUX内核简介 1.1 Unix的历史 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统. 1973年,整个Unix系统用C语言进行了重写,给后 ...
- Linux 实验一 基础实践
Linux 实践一 1:软件源的维护方法 删掉DEB打头的 在命令行中输入命令时,可以用命令补全的方法. 下载完成后,使用sudo dpkg-i skype.deb 来完成安装. 2:掌握Linux ...
- SRS用例
团队项目:超市管理系统 作者:王琨 个人博客地址:http://www.cnblogs.com/wangkun123 一. 用例视图概述 一般的超市商品管理系统,主要由五大模块组成,即商品信 ...