[BZOJ4013][HNOI2015]实验比较-树形DP

实验比较

Description

小D 被邀请到实验室,做一个跟图片质量评价相关的主观实验。实验用到的图片集一共有 N 张图片,编号为 1 到 N。实验分若干轮进行,在每轮实验中,小 D会被要求观看某两张随机选取的图片, 然后小D 需要根据他自己主观上的判断确定这两张图片谁好谁坏,或者这两张图片质量差不多。 用符号”<“、”>“和”=“表示图片 x和y(x、y为图片编号)之间的比较:

如果上下文中 x 和 y 是图片编号,则 x < y 表示图片 x”质量优于“y,x>y 表示图片 x”质量差于“y,x=y表示图片 x和 y”质量相同“;

也就是说,这种上下文中,”<“、”>“、”=“分别是质量优于、质量差于、质量相同的意思;在其他上下文中,这三个符号分别是小于、大于、等于的含义。图片质量比较的推理规则(在 x和y是图片编号的上下文中):

(1)x < y等价于 y > x。

(2)若 x < y 且y = z,则x < z。

(3)若x < y且 x = z,则 z < y。

(4)x=y等价于 y=x。

(5)若x=y且 y=z,则x=z。

实验中,小 D 需要对一些图片对(x, y),给出 x < y 或 x = y 或 x > y 的主观判断。小D 在做完实验后, 忽然对这个基于局部比较的实验的一些全局性质产生了兴趣。在主观实验数据给定的情形下,定义这 N 张图片的一个合法质量序列为形如”x1 R1 x2 R2 x3 R3 …xN-1 RN-1 xN“的串,也可看作是集合{ xi Ri xi+1|1<=i<=N-1},其中 xi为图片编号,x1,x2,…,xN两两互不相同(即不存在重复编号),Ri为<或=,”合法“是指这个图片质量序列与任何一对主观实验给出的判断不冲突。

例如: 质量序列3 < 1 = 2 与主观判断”3 > 1,3 = 2“冲突(因为质量序列中 3<1 且1=2,从而3<2,这与主观判断中的 3=2 冲突;同时质量序列中的 3<1 与主观判断中的 3>1 冲突) ,但与主观判断”2 = 1,3 < 2“ 不冲突;因此给定主观判断”3>1,3=2“时,1<3=2 和1<2=3 都是合法的质量序列,3<1=2 和1<2<3都是非法的质量序列。由于实验已经做完一段时间了,小D 已经忘了一部分主观实验的数据。对每张图片 i,小 D 都最多只记住了某一张质量不比 i 差的另一张图片 Ki。这些小 D 仍然记得的质量判断一共有 M 条(0 <= M <= N),其中第i 条涉及的图片对为(KXi, Xi),判断要么是KXi < Xi ,要么是KXi = Xi,而且所有的Xi互不相同。小D 打算就以这M 条自己还记得的质量判断作为他的所有主观数据。

现在,基于这些主观数据,我们希望你帮小 D 求出这 N 张图片一共有多少个不同的合法质量序列。

我们规定:如果质量序列中出现”x = y“,那么序列中交换 x和y的位置后仍是同一个序列。因此: 1<2=3=4<5 和1<4=2=3<5 是同一个序列, 1 < 2 = 3 和 1 < 3 = 2 是同一个序列,而1 < 2 < 3 与1 < 2 = 3是不同的序列,1<2<3和2<1<3 是不同的序列。由于合法的图片质量序列可能很多, 所以你需要输出答案对10^9 + 7 取模的结果

Input

第一行两个正整数N,M,分别代表图片总数和小D仍然记得的判断的条数;
接下来M行,每行一条判断,每条判断形如”x < y”或者”x = y”。

Output

输出仅一行,包含一个正整数,表示合法质量序列的数目对 10^9+7取模的结果。

Sample Input

5 4
1 < 2
1 < 3
2 < 4
1 = 5

Sample Output

5

HINT

不同的合法序列共5个,如下所示:

1 = 5 < 2 < 3 < 4
1 = 5 < 2 < 4 < 3
1 = 5 < 2 < 3 = 4
1 = 5 < 3 < 2 < 4
1 = 5 < 2 = 3 < 4

100%的数据满足N<=100。


试图 O ( n 2 ) 了接近1h才看到 n 100 ……

(╯‵□′)╯︵┻━┻


思路:
首先判合法显然是用并查集了~

对于相等关系,考虑使用并查集缩点即可。
注意到除了相等外仅存在小于关系,那么一旦合法,最后得到的图会是一个DAG。
然后,注意到 m n ,那么最后得到的图将会是一个森林。
于是建立一个超级根节点链接森林里所有根节点,以便同时处理。

考虑一个节点 u 的两个不同的儿子序列。
在考虑计算以 u 为根的答案时,可以发现,答案以 u 处的图片开头为一个相等段,后接的其他图片满足在保证对应子树中偏序的同时任意排列。
也就是,两个序列拼在一起,满足原来每个序列的元素位置大小关系在合并后的新序列中依旧不变。

假设 u 只有两个儿子 a b
考虑到这样的拼接方案必然是相互交错的若干连续段,满足每一段之间值相等。
形如下方:

令A1,A2...An 为一种合法的a序列,B1,B2...同理。
一种合法的拼接方案形如
A1 B1 A2A3 B2B3 A4...

那么考虑DP,设 f [ i ] [ j ] 代表将 i 子树内的节点划分成 j 段的方案数。
于是遍历 u 的每个儿子 v ,每当遍历到一个新儿子,则有
f [ u ] [ i ] = f [ u ] [ j ] f [ v ] [ k ] ( j 1 i 1 ) ( k i + j j 1 )
意思是,先选出 j 1 个位置放置原序列,再选择 k i + j 个放置了原序列的地方与 v 处序列合并,剩下的 v 处的元素放入剩下空位。

然后dfs一发即可得到答案!

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int,int> pr;
const int N=109;
const ll md=1e9+7;

int n,m;
int fa[N],val[N],id[N],len[N],fat[N];
int isson[N],to[N<<1],nxt[N<<1],beg[N],tot;
ll fac[N],inv[N],f[N][N],g[N];
vector<pr> vec;

inline ll qpow(ll a,ll b)
{
    ll ret=1;
    while(b)
    {
        if(b&1)ret=ret*a%md;
        a=a*a%md;b>>=1;
    }
    return ret;
}

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
    isson[v]=1;
}

inline ll c(int a,int b)
{
    return fac[a]*inv[b]%md*inv[a-b]%md;
}

inline int find(int x)
{
    return fa[x]=fa[x]==x?x:find(fa[x]);
}

inline bool merge(int x,int y)
{
    if(find(x)==find(y))return 1;
    else return fa[fa[y]]=fa[x],0;
}

inline void dfs(int u)
{
    f[u][1]=len[u]=1;
    for(int i=beg[u];i;i=nxt[i])
    {
        dfs(to[i]);
        for(int j=1;j<=len[u]+len[to[i]];j++)
            g[j]=0;
        for(int l=1;l<=len[u]+len[to[i]];l++)
            for(int j=1;j<=len[u];j++)
                for(int k=1;k<=len[to[i]];k++)
                    if(k-l+j>=0)
                        (g[l]+=f[u][j]*f[to[i]][k]%md*c(l-1,j-1)%md*c(j-1,k-l+j)%md)%=md;
        len[u]+=len[to[i]];
        for(int l=1;l<=len[u];l++)
            f[u][l]=g[l];
    }
}

int main()
{
    scanf("%d%d",&n,&m);

    fac[0]=1;
    for(ll i=1;i<=n;i++)
        fac[i]=fac[i-1]*i%md;
    inv[n]=qpow(fac[n],md-2);
    for(ll i=n;i>=1;i--)
        inv[i-1]=inv[i]*i%md;
    for(int i=1;i<=n;i++)
        fa[i]=i;

    char s[10];
    for(int i=1,a,b;i<=m;i++)
    {
        scanf("%d%s%d",&a,s+1,&b);
        if(s[1]=='=')merge(a,b);
        else 
        {
            if(s[1]=='>')swap(a,b);
            vec.push_back(pr(a,b));
        }
    }

    for(int i=1;i<=n;i++)
        val[find(i)]++,fat[i]=find(i);

    for(int i=0;i<vec.size();i++)
    {
        add(fat[vec[i].first],fat[vec[i].second]);
        if(merge(vec[i].first,vec[i].second))
            return puts("0"),0;
    }
    for(int i=1;i<=n;i++)
        if(!isson[i] && find(i)==i)
            add(n+1,i);

    dfs(n+1);ll ans=0;
    for(int i=1;i<=len[n+1];i++)
        ans=(ans+f[n+1][i])%md;
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zlttttt/article/details/79674268
今日推荐