4013: [HNOI2015]实验比较

44 篇文章 0 订阅

4013: [HNOI2015]实验比较

Time Limit: 5 Sec   Memory Limit: 512 MB
Submit: 535   Solved: 268
[ Submit][ Status][ Discuss]

Description

被邀请到实验室,做一个跟图片质量评价相关的主观实验。实验用到的图片集一共有  N  张图片,编号为  1   N 。实验分若干轮进行,在每轮实验中,小  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  的主观判断。小 在做完实验后,  忽然对这个基于局部比较的实验的一些全局性质产生了兴趣。在主观实验数据给定的情形下,定义这  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 都是非法的质量序列。由于实验已经做完一段时间了,小 已经忘了一部分主观实验的数据。对每张图片  i ,小  D  都最多只记住了某一张质量不比  i  差的另一张图片  Ki 。这些小  D  仍然记得的质量判断一共有  M  条( 0 <= M <= N ),其中第 条涉及的图片对为 (KXi, Xi) ,判断要么是 KXi   < Xi   ,要么是 KXi = Xi ,而且所有的 Xi 互不相同。小 打算就以这 条自己还记得的质量判断作为他的所有主观数据。现在,基于这些主观数据,我们希望你帮小  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。  

Source

[ Submit][ Status][ Discuss]

注意到对于第i张照片,不比i差的照片最多只有一张
首先用并查集把所有等号合并,如果有关系i < j,那么连一条有向边(i,j)
如果无解,肯定会形成环,否则,对所有入度为0的点,连有向边(0,i),这样就将原图构成了一棵树
考虑树上dp,定义f[i][j]:以i为根的子树中,分成了j种等级,方案数
转移的话,考虑逐个处理好儿子k的情况,将所有情况暴力枚举然后合并到i里
这样就变成,需求将一个a种等级的序列和另一个b种等级的序列合并成k种等级的序列的方案数
合并成k种等级等于有k个坑要填,同一个序列中任意两个数不能放在同一个坑里
但是不同序列中的两个数就没有这样的限制了
不妨先将序列a的数字放入坑中,共C[k][a]种填法
然后把b的也放进来,先要把空位填满,然后和a中任意一些数字合并
所以方案共是C[k][a] * C[a][a + b - k]

复杂度的话,看似O(N^4),但是,每个点对只会在其LCA处被枚举到并产生O(N)的运算量
精细地实现的话复杂度是O(N^3)的
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
 
const int maxn = 111;
typedef long long LL;
const LL mo = 1000000007;
const char D[3] = {'<','=','>'};
 
int n,m,fa[maxn],du[maxn],siz[maxn],f[maxn][maxn],h[maxn],C[maxn][maxn];
bool G[maxn][maxn],vis[maxn];
 
vector <int> v[maxn],g[maxn];
queue <int> Q;
 
int Mul(const LL &x,const LL &y) {return x * y % mo;}
int Add(const int &x,const int &y) {return (x + y) % mo;}
int getfa(int k) {return k == fa[k] ? k : fa[k] = getfa(fa[k]);}
 
int Read()
{
    char ch = getchar();
    while (ch != '<' && ch != '=' && ch != '>') ch = getchar();
    for (int i = 0; ; i++) if (ch == D[i]) return i;
}
 
int getint()
{
    char ch = getchar(); int ret = 0;
    while (ch < '0' || '9' < ch) ch = getchar();
    while ('0' <= ch && ch <= '9')
        ret = ret * 10 + ch - '0',ch = getchar();
    return ret;
}
 
void Dfs(int x)
{
    siz[x] = f[x][1] = 1;
    for (int I = 0; I < v[x].size(); I++)
    {
        int y = v[x][I]; Dfs(y);
        for (int i = 0; i < siz[x]; i++)
            for (int j = 1; j <= siz[y]; j++)
                for (int k = max(i,j); k <= i + j; k++)
                {
                    int A = Mul(f[x][i+1],f[y][j]);
                    int B = Mul(C[k][i],C[i][j-k+i]);
                    h[k+1] = Add(h[k+1],Mul(A,B));
                }
        memcpy(f[x],h,sizeof(h)); memset(h,0,sizeof(h)); siz[x] += siz[y];
    }
}
 
int main()
{
    #ifdef DMC
        freopen("DMC.txt","r",stdin);
    #endif
     
    n = getint(); m = getint(); C[0][0] = 1;
    for (int i = 1; i <= n; i++)
    { 
        C[i][0] = 1;
        for (int j = 1; j <= i; j++)
            C[i][j] = Add(C[i-1][j],C[i-1][j-1]);
    }
    for (int i = 1; i <= n; i++) fa[i] = i;
    while (m--)
    {
        int x = getint(),typ,y;
        typ = Read(); y = getint();
        if (!typ) g[x].push_back(y);
        else if (typ == 2) g[y].push_back(x);
        else
        {
            int fx = getfa(x),fy = getfa(y);
            if (fx != fy) fa[fx] = fy;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        int x = getfa(i);
        for (int j = 0; j < g[i].size(); j++)
        {
            int y = getfa(g[i][j]);
            if (!G[x][y]) v[x].push_back(y),++du[y],G[x][y] = 1;
        }
    }
    for (int i = 1; i <= n; i++)
        if (!du[i] && fa[i] == i) ++du[i],v[0].push_back(i);
     
    Q.push(0);
    while (!Q.empty())
    {
        int k = Q.front(); Q.pop(); vis[k] = 1;
        for (int i = 0; i < v[k].size(); i++)
        {
            int to = v[k][i]; --du[to];
            if (!du[to]) Q.push(to);
        }
    }
    for (int i = 1; i <= n; i++) if (!vis[i] && fa[i] == i) {puts("0"); return 0;}
    Dfs(0); int Ans = 0;
    for (int i = 1; i <= siz[0]; i++) Ans = Add(Ans,f[0][i]);
    cout << Ans << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值