[HNOI2015]实验比较

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\)张图片的一个合法质量序列为形如"\(x_1\,R_1\,x_2\,R_2\,x_3\,R_3\,…x_{N-1}\,R_{N-1}\,x_N\)"的串,也可看作是集合\(\{x_i\,R_i\,x_{i+1}|1\leqslant i\leqslant N-1\}\),其中\(x_i\)为图片编号,\(x_1,x_2,…,x_N\)两两互不相同(即不存在重复编号),\(R_i\)为<或=,"合法"是指这个图片质量序列与任何一对主观实验给出的判断不冲突。

例如: 质量序列\(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\)差的另一张图片\(K_i\)。这些小\(D\)仍然记得的质量判断一共有\(M\)\((0\leqslant M\leqslant N)\),其中第\(i\)条涉及的图片对为\((K_{X_i},X_i)\),判断要么是\(K_{X_i}<X_i\),要么是\(K_{X_i}=X_i\),而且所有的\(X_i\)互不相同。小\(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。


首先注意到题目中很重要的一句话,对每张图片\(i\),小\(D\)都最多只记住了某一张质量不比\(i\)差的另一张图片\(K_i\),于是我们将所有等于关系的点缩到一起,对于点\(i\),如果\(K_i\)存在,则\(i\)\(K_i\)连边,那么显然构成了森林,我们用超级root把森林变成树

然后tree dp,设\(f[u]\)表示\(u\)的子树内的方案数

但是对于\(u\)的两个子树\(v,w\),可能存在一些点质量相同,因此我们需要再添加一维,设\(f[u][i]\)表示\(u\)的子树内分成\(i\)端的方案数(每段里面的节点质量相等),设\(f'\)表示更新之前的\(f\)值,转移有

\[f[u][i]=\sum\limits_{j,k}f'[u][j]\times f[v][k]\times T\]

\(T\)表示\(j\)段和\(k\)段合并成\(i\)段的方案数

\(f[u]\)的质量序列为\(A\)\(f'[u]\)的质量序列为\(B\)\(f[v]\)的质量序列为\(C\),把他们合并起来相当于枚举\(B\)中的\(j-1\)段在\(A\)中的位置(起始端点已经被\(u\)霸占),那么方案为\(\binom{i-1}{j-1}\),然后把\(C\)中的\(i-j\)段放置到\(A\)中使得\(A\)非空,剩下的与\(B\)合并,方案为\(\binom{j-1}{k-i+j}\),则\(T=\binom{i-1}{j-1}\times\binom{j-1}{k-i+j}\)

答案即为\(Ans=\sum f[root][i]\),由于每对点都只在lca处被计算贡献了\(O(n)\)次,因此复杂度为\(O(n^3)\)

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline char gc(){
    static char buf[1000000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline int frd(){
    int x=0,f=1; char ch=gc();
    for (;ch<'0'||ch>'9';ch=gc())   if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=gc()) x=(x<<3)+(x<<1)+ch-'0';
    return x*f;
}
inline int read(){
    int x=0,f=1; char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())  if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())    x=(x<<3)+(x<<1)+ch-'0';
    return x*f;
}
inline void print(int x){
    if (x<0)    putchar('-'),x=-x;
    if (x>9)    print(x/10);
    putchar(x%10+'0');
}
const int N=1e2,Mod=1e9+7;
int pre[(N<<1)+10],now[N+10],child[(N<<1)+10],tot;
int fa[N+10],X[N+10],Y[N+10],top[N+10],dge[N+10],size[N+10];
int f[N+10][N+10],C[N+10][N+10],Ans;
bool L[N+10],Endl[N+10];
void prepare(){
    for (int i=0;i<=N;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;
    }
}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
bool merge(int x,int y){
    x=find(x),y=find(y);
    return x!=y?fa[x]=y,0:1;
}
void join(int x,int y){pre[++tot]=now[x],now[x]=tot,child[tot]=y;}
void insert(int x,int y){join(x,y),join(y,x);}
void dfs(int x,int fa){
    f[x][1]=size[x]=1;
    for (int p=now[x],son=child[p];p;p=pre[p],son=child[p]){
        if (son==fa)    continue;
        dfs(son,x);
        static int g[N+10];
        for (int i=1;i<=size[x]+size[son];i++){
            g[i]=0;
            for (int j=1;j<=size[x];j++){
                for (int k=1;k<=size[son];k++){
                    if (k-i+j<0)    continue;
                    g[i]=(g[i]+1ll*f[x][j]*f[son][k]%Mod*C[i-1][j-1]%Mod*C[j-1][k-i+j]%Mod)%Mod;
                }
            }
        }
        for (int i=1;i<=size[x]+size[son];i++)  f[x][i]=g[i];
        size[x]+=size[son];
    }
}
int main(){
    prepare();
    int n=read(),m=read();
    for (int i=1;i<=n;i++)  fa[i]=i;
    for (int i=1;i<=m;i++){
        char ch[5];
        scanf("%d%s%d",X+i,ch,Y+i);
        L[i]=(ch[0]=='=');
        if (L[i])   merge(X[i],Y[i]);
    }
    for (int i=1;i<=n;i++)  Endl[top[i]=find(i)]=1;
    for (int i=1;i<=n;i++)  fa[i]=i;
    for (int i=1;i<=m;i++){
        if (L[i])   continue;
        insert(top[X[i]],top[Y[i]]);
        dge[top[Y[i]]]++;
        if (merge(top[X[i]],top[Y[i]])) return printf("0\n"),0;
    }n++;
    for (int i=1;i<n;i++)   if (Endl[i]&&!dge[i])   insert(n,i);
    dfs(n,0);
    for (int i=1;i<=size[n];i++)    Ans=(Ans+f[n][i])%Mod;
    printf("%d\n",Ans);
    return 0;
}

转载于:https://www.cnblogs.com/Wolfycz/p/10253944.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值