[GDOI模拟2015.12.19][HEOI2013]SAO

题目大意

n 个事件有n1个约束,表示为第 i 个事件必须在第j个之前(后)发生。求可能的事件排列(答案模 109+7 )。
将所有约束视为无向边,满足 n 个事件在一个联通块内。
1n1000


题目分析

一眼看去,题目条件和树有关,但是又有边的方向的限制,一下子好像变得很棘手。这是容易将题目看做拓扑图来解决,无异于给自己添麻烦。
其实我们将其看成一棵树,做树形dp,对于不同方向的边不同处理就行了。
约定:
fx,i 表示节点 x 在以x为根的子树中第 i 个发生,可能的排列数。
prex,i sufx,i 分别为 fx,i 的前后缀和。
size(x) 为以 x 为根的子树的节点个数。
对于节点x,枚举到儿子节点 y ,我们先递归处理y。这时我们已经知道了 fy 以及 fx (节点 x 和已经处理的子树的dp答案),我们要递推出新的fx,为避免重复,我们记新的 fx,i gi 。设 s 为节点x和已经处理的子树的节点个数总和。
现在我们要合并 x 的部分和y的部分。
对于 x 先于y发生,采用如下分析方法:
i 表示x位于 x 所在部分的第i个, j 表示合并后x之前有 j1 个是属于 y 所在部分(那么y y 所在部分的排名就大于等于j)。
于是我们可以列出递推方程:

gi+j1=(i+j2i1)×(si+size(y)j+1si)×fx,i×sufy,j

对于 x 后于y发生,我们采用相似的分析方法:
i 表示x位于 x 所在部分的第i个, j 表示合并后x之前有 j 个是属于y所在部分的(那么 y y所在部分的排名就小于等于 j
同样可以列出递推方程:
gi+j=(i+j1i1)×(si+size(y)jsi)×fx,i×prey,j

然后我们就可以将 g 赋值给fx并清空。
然后最终答案即是 preroot,n sufroot,1
时间复杂度理论是 O(n3) ,但是实际上枚举的状态达不到那么多,于是就飞起了。


代码实现

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch))
    {
        if (ch=='-')
            f=-1;
        ch=getchar();
    }
    while (isdigit(ch))
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}

const int N=1000;
const int M=N-1;
const int E=M<<1;
const int P=1000000007;

long long f[N+1][N+1],pre[N+1][N+1],suf[N+1][N+1],g[N+1],fact[N+1],invf[N+1];
int size[N+1],last[N+1];
int tov[E+1],next[E+1];
int tot,n,m,ans,T;
bool cst[E+1];

void insert(int x,int y,bool z)
{
    tov[++tot]=y;
    cst[tot]=z;
    next[tot]=last[x];
    last[x]=tot;
}

long long quick_power(int x,int y)
{
    if (!y)
        return 1;
    if (y==1)
        return x;
    long long ret=quick_power(x,y>>1);
    ret=(long long)ret*ret%P;
    if (y&1)
        ret=(long long)ret*x%P;
    return ret;
}

void preparation()
{
    fact[0]=1;
    for (int i=1;i<=N;i++)
        fact[i]=(long long)fact[i-1]*i%P;
    invf[N]=quick_power(fact[N],P-2);
    for (int i=N-1;i>=0;i--)
        invf[i]=(long long)invf[i+1]*(i+1)%P;
}

long long combination(int n,int m)
{
    return (long long)fact[n]*invf[m]%P*invf[n-m]%P;
}

void dfs(int x,int fa)
{
    size[x]=1;
    int i=last[x],y;
    bool found=false;
    while (i)
    {
        y=tov[i];
        if (y!=fa)
        {
            dfs(y,x);
            size[x]+=size[y];
            found=true;
        }
        i=next[i];
    }
    if (!found)
    {
        f[x][1]=1;
        pre[x][1]=f[x][1];
        for (i=2;i<=n;i++)
            pre[x][i]=(pre[x][i-1]+f[x][i])%P;
        suf[x][n]=f[x][n];
        for (i=n-1;i>=1;i--)
            suf[x][i]=(suf[x][i+1]+f[x][i])%P;
        return;
    }
    int s=1;
    for (int i=1;i<=n;i++)
        f[x][i]=0;
    f[x][1]=1;
    i=last[x];
    while (i)
    {
        y=tov[i];
        if (y!=fa)
        {
            for (int k=1;k<=s+size[y];k++)
                g[k]=0;
            for (int k=1;k<=s;k++)
                for (int l=1;l<=size[y];l++)
                    if (cst[i])
                        (g[k+l-1]+=(long long)combination(k+l-2,k-1)*combination(s-k+size[y]-l+1,s-k)%P*f[x][k]%P*suf[y][l]%P)%=P;
                    else
                        (g[k+l]+=(long long)combination(k+l-1,k-1)*combination(s-k+size[y]-l,s-k)%P*f[x][k]%P*pre[y][l]%P)%=P;
            for (int k=1;k<=s+size[y];k++)
                f[x][k]=g[k];
            s+=size[y];
        }
        i=next[i];
    }
    pre[x][1]=f[x][1];
    for (i=2;i<=n;i++)
        pre[x][i]=(pre[x][i-1]+f[x][i])%P;
    suf[x][n]=f[x][n];
    for (i=n-1;i>=1;i--)
        suf[x][i]=(suf[x][i+1]+f[x][i])%P;
}

int main()
{
    preparation();
    freopen("sao.in","r",stdin);
    freopen("sao.out","w",stdout);
    T=read();
    while (T--)
    {
        memset(last,0,sizeof last);
        memset(next,0,sizeof next);
        memset(f,0,sizeof f);
        tot=0;
        n=read();
        for (int i=1,x,y;i<n;i++)
        {
            x=read()+1;
            char sign=getchar();
            while (sign!='<'&&sign!='>')
                sign=getchar();
            y=read()+1;
            insert(x,y,sign=='<');
            insert(y,x,sign!='<');
        }
        dfs(1,0);
        ans=0;
        for (int i=1;i<=n;i++)
            (ans+=f[1][i])%=P;
        printf("%d\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值