洛谷P3240 [HNOI2015]实验比较

链接

点击跳转

题解

d p i , j dp_{i,j} dpi,j表示以 i i i为根的子树,分成 j j j段的方案数

状态解释: j j j段,每一段内部都是等号连接的,段与段之间是小于号连接的

做树形 d p dp dp,合并的时候,假设用 d p [ u ] [ …   ] dp[u][\dots] dp[u][] d p [ v ] [ …   ] dp[v][\dots] dp[v][]去合并得到 d p [ w ] [ …   ] dp[w][\dots] dp[w][]

d p [ u ] [ i ] × d p [ v ] [ j ] dp[u][i]\times dp[v][j] dp[u][i]×dp[v][j] d p [ w ] [ k ] dp[w][k] dp[w][k]的贡献分两步计算:

先从 u u u中选择 i i i段出来,再从 v v v中选 j j j段出来,然后再把这 i i i段和 j j j段合并起来,形成 k k k

合并的时候,必须是来自不同子树的才可以合并成同一块

假设把 i i i个来自第一棵子树的块和 j j j个来自第二课子树的块合并成 k k k个块,第一块必须是来自第一棵子树中的第一块,而且每个块不能为空,每个块不能包含来自同一棵子树的两块。满足上述条件的方案数记作 g i j k g_{ijk} gijk

那么这个 d p dp dp写出来就是

d p w , k = ∑ i , j d p u , i × d p v , j × g i j k dp_{w,k} = \sum_{i,j} dp_{u,i} \times dp_{v,j} \times g_{ijk} dpw,k=i,jdpu,i×dpv,j×gijk

任何两个点只在 l c a lca lca处进行一次 O ( n ) O(n) O(n)(树为一条链的时候取到 O ( n ) O(n) O(n),否则更小)的合并,所以最终复杂度是 O ( n 3 ) O(n^3) O(n3)

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 110
#define maxe 210
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
struct Graph
{
    int etot, head[maxn], to[maxe], next[maxe], w[maxe];
    void clear(int N)
    {
        for(int i=1;i<=N;i++)head[i]=0;
        etot=0;
    }
    void adde(int a, int b, int c=0){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
    #define forp(pos,G) for(auto p=G.head[pos];p;p=G.next[p])
}G;
struct UnionFind
{
	ll f[maxn], size[maxn];
	void init(ll n)
	{
		for(auto i=1;i<=n;i++)f[i]=i, size[i]=1;
	}
	ll find(ll x){return f[x]==x?x:f[x]=find(f[x]);}
	void merge(ll x, ll y)
    {
        auto fx=find(x), fy=find(y);
        f[fx]=fy;
        if(fx!=fy)size[fy]+=size[fx];
    }
}uf, uf2;
#define mod 1'000'000'007
ll n, dp[maxn][maxn], fa[maxn], indeg[maxn], sz[maxn], m, tmp[maxn], f[maxn][maxn][maxn];
vector< tuple<ll,char,ll> > lis;
void merge(ll u, ll v)
{
    ll i, j, k, mx=sz[u]+sz[v];
    rep(k,1,mx)tmp[k]=0;
    rep(k,1,mx)
    {
        rep(i,1,sz[u])rep(j,1,sz[v])(tmp[k]+=dp[u][i]*dp[v][j]%mod*f[i][j][k])%=mod;
    }
    rep(k,1,mx)dp[u][k]=tmp[k];
}
void dfs(ll pos)
{
    dp[pos][1]=1;
    sz[pos]=1;
    forp(pos,G)
    {
        auto to=G.to[p];
        dfs(to);
        merge(pos,to);
        sz[pos]+=sz[to];
    }
}
int main()
{
    ll i, j, k;
    n=read(), m=read();
    uf.init(n);
    uf2.init(n+1);
    rep(i,1,m)
    {
        ll f, u; char s[maxn];
        scanf("%lld%s%lld",&f,s,&u);
        lis.emb( make_tuple(f,*s,u) );
    }
    for(auto tp:lis)
    {
        ll f=get<0>(tp), c=get<1>(tp), u=get<2>(tp);
        if(c=='=')uf.merge(u,f);
    }
    for(auto tp:lis)
    {
        ll f=get<0>(tp), c=get<1>(tp), u=get<2>(tp);
        if(c=='<')
        {
            fa[uf.find(u)]=uf.find(f);
            indeg[uf.find(f)]++;
        }
    }
    rep(i,1,n)if(uf.find(i)==i and fa[i]==0)
    {
        fa[i]=n+1;
        indeg[n+1]++;
    }
    rep(i,1,n)if(uf.find(i)==i)
    {
        G.adde(fa[i],i);
        if(uf2.find(fa[i])!=uf2.find(i))
        {
            uf2.merge(fa[i],i);
        }
        else
        {
            printf("0");
            return 0;
        }
    }
    f[1][0][1]=1;
    for(k=2;k<=n+1;k++)rep(i,0,n+1)rep(j,0,n+1)
    {
        if(i)f[i][j][k]+=f[i-1][j][k-1];
        if(j)f[i][j][k]+=f[i][j-1][k-1];
        if(i and j)f[i][j][k]+=f[i-1][j-1][k-1];
        f[i][j][k]%=mod;
    }
    dfs(n+1);
    ll ans=0;
    rep(i,1,n+1)(ans+=dp[n+1][i])%=mod;
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值