题目大意:
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=1000+10; const int mod=1e9+7; int n,t; struct node{ int nxt,to,val; }bian[2*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]=1; f[x][1]=1; vis[x]=1; 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>=1;k--) { ull sum=0; for(int i=1;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-1][i-1]*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>=1;k--) { ull sum=0; for(int i=1;i<=min(size[x],k-1);i++) { int r=min(size[y],k-i); ull del=sumdp[y][r]; ull q=(f[x][i]*del)%mod,p=(c[k-1][i-1]*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=1;i<=size[x];i++)//处理完了x,赋值前缀和,以便后续使用 sumdp[x][i]=(sumdp[x][i-1]+f[x][i])%mod; } void clear()//清空 { cnt=0; for(int i=0;i<=n;i++) { head[i]=0;vis[i]=0; size[i]=1; for(int j=0;j<=n;j++) sumdp[i][j]=0,f[i][j]=0; } } int main() { c[0][0]=1; for(int i=1;i<=1005;i++) { c[i][0]=1; for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod; }//1000的范围,组合数打表 cin>>t; while(t) { scanf("%d",&n); clear(); int x,y; char q[3]; for(int i=1;i<=n-1;i++) { scanf("%d%s%d",&x,q,&y); x++,y++;//变成以1开始 if(q[0]=='<') { add(x,y,1); add(y,x,0); } else{ add(y,x,1); add(x,y,0); }//建无向边,x,y距离是1,表示x<y 先过x后过 y } dfs(1); ull ans=0; for(int i=1;i<=size[1];i++) { ans=(ans+f[1][i])%mod; }//方案数 printf("%llu ",ans); t--; } return 0; }
基本思路和代码参考shadowice1984,https://www.luogu.org/blog/ShadowassIIXVIIIIV/solution-p4099
详细化了很多。