【bzoj4013】 HNOI2015—实验比较
http://www.lydsy.com/JudgeOnline/problem.php?id=4013 (题目链接)
题意
给出$n$个数的$m$个大小关系,问它们之间可以形成的单调不降的序列有多少种。
Solution
首先,因为等于号相连的两个数位置互换不会产生新的方案,我们先用并查集把用等号相连的点全部缩成一个。如果此时的图中出现了环,那么答案为$0$。考虑答案不为$0$的情况怎么处理。此时的图已经成为了一个DAG,我们需要在上面统计方案。容易发现,对于一个点,有分有合,合的情况很好处理,分的情况就很尴尬了,什么?你说每一个点只有一条入边?(哔了狗了)。因为并查集缩点后整个图已经变成了一棵树,我们考虑如何进行树形dp。
$f[x][i]$表示在$x$的子树中,组成的序列用$<$相连的等价类个数为$i$个的序列方案,其中等价类就表示由等号相连的一坨数。不妨设$y$是$x$的某个儿子,那么转移:
\begin{aligned} g[i+l]=f[x][i]*f[y][j]*\binom{i-1+l}{j-1}*\binom{j-1}{k-l} \end{aligned}
其中$i\in[1,size[x]]$,$j\in[1,size[y]]$,$l\in[max(0,k-j+1),k]$。$g$是一个临时的存储答案的数组。$l$是我们枚举的$y$所贡献的等价类,那么剩下的$k-l$就是$y$中与原本$x$中相等的数的个数。$x$永远排在序列首位而且不会与任意一个数相等。$\binom{i-1+l}{j-1}$表示两个无相对关系的已经排序好的序列合并为一个序列的方案。$\binom{j-1}{k-l}$表示在$x$的$j-1$个数中选出与$y$的$k-l$个数相等的数的方案。
细节
可能是个森林,所以用一个超级源点连向若干根节点。
代码
// bzoj4013 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #include<queue> #define LL long long #define inf (1ll<<30) #define MOD 1000000007 #define Pi acos(-1.0) #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout); using namespace std; const int maxn=200; LL C[maxn][maxn],f[maxn][maxn],g[maxn]; int head[maxn],size[maxn],vis[maxn],fa[maxn],r[maxn],n,m,c,cnt,sum; struct data {int u,v;}a[maxn]; struct edge {int to,next;}e[maxn]; int find(int x) { return x==fa[x] ? x : fa[x]=find(fa[x]); } void link(int u,int v) { e[++cnt]=(edge){v,head[u]};head[u]=cnt; } bool bfs() { queue<int> q;q.push(0); int tot=0; while (!q.empty()) { int x=q.front();q.pop();tot++; for (int i=head[x];i;i=e[i].next) if (!--r[e[i].to]) q.push(e[i].to); } return tot==sum+1; } void dfs(int x) { vis[x]=f[x][1]=size[x]=1; for (int i=head[x];i;i=e[i].next) if (!vis[e[i].to]) { int y=e[i].to;dfs(y); for (int j=1;j<=size[x];j++) { if (!f[x][j]) continue; for (int k=1;k<=size[y];k++) { if (!f[y][k]) continue; for (int l=max(0,k-j+1);l<=k;l++) (g[j+l]+=f[x][j]*f[y][k]%MOD*C[j+l-1][j-1]%MOD*C[j-1][k-l]%MOD)%=MOD; } } size[x]+=size[e[i].to]; for (int i=1;i<=size[x];i++) f[x][i]=g[i],g[i]=0; } } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) fa[i]=i; for (int i=0;i<=n;i++) C[i][0]=1; for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD; for (int u,v,i=1;i<=m;i++) { char ch[2];scanf("%d%s%d",&u,ch,&v); if (ch[0]=='=') if (find(u)!=find(v)) fa[find(u)]=find(v); if (ch[0]=='<') a[++c]=(data){u,v}; } for (int i=1;i<=c;i++) { int u=find(a[i].u),v=find(a[i].v); link(u,v);r[v]++; } for (int i=1;i<=n;i++) if (fa[i]==i) { sum++; if (!r[i]) link(0,i),r[i]++; } if (!bfs()) {puts("0");return 0;} dfs(0); int ans=0; for (int i=1;i<=n+1;i++) (ans+=f[0][i])%=MOD; printf("%d",ans); return 0; }