[BZOJ4770]图样

题意简述

n 个节点,每个节点的权值为0 2m1 的随机值。
两个节点 i,j 之间的边权为 AixorAj ,求最小生成树的期望边权和。
答案对 258280327 取模。

数据范围

1n50
1m8

思路

看到xor考虑每一位单独考虑。
我们计算 h[k][i] 表示考虑 k 位,点数为i的所有生成树的边权和。
有一个性质:假设最高位为0的集合为 S1 ,最高位为1的集合为 S2 S1 S2 之间仅有一条边相连。
我们这样就可以把 i 分成两个集合来做。
枚举i集合中最高位为1的数量,转移。
现在需要求出两个集合中间连边的边权。
f[k][i][j][S] 表示考虑 k 位,在两个大小为i j 的集合之间连一条边,边的权值>S的方案数。
枚举 i 集合和j集合中最高位为1的数量。
逐位贪心,如果最高位有相同的肯定在最高位相同的之间连边,分类讨论一波。
g[k][i][j] 表示考虑 k 位,i j 两集合之间的边权和,直接把f[][][][S]加起来就好了= =考虑贡献。
最后把总和除以方案数 2nm 就行了,也可以直接DP期望。
复杂度 O(n42m) 比较卡
打表大法好。

代码

#include<cstdio>
using namespace std;
const int p=258280327;
int n,m;
int C[55][55],f[10][55][55][300],g[10][55][55],h[10][55],pw2[3010];
int quick_pow(int a,int x)
{
    long long ret=1;
    for (long long sum=a;x;sum=sum*sum%p,x>>=1)
        if (x&1)
            ret=ret*sum%p;
    return (int)ret;
}
int main()
{
    scanf("%d%d",&n,&m);
    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]=(C[i-1][j-1]+C[i-1][j])%p;
    }
    pw2[0]=1;
    for (int i=1;i<=3000;i++)
        pw2[i]=pw2[i-1]*2%p;
    for (int i=0;i<=n;i++)
        for (int j=0;j<=n;j++)
            if (i==0||j==0)
            {
                f[0][i][j][0]=pw2[i+j];
                f[0][i][j][1]=pw2[i+j];
            }
            else
            {
                f[0][i][j][0]=pw2[i+j];
                f[0][i][j][1]=2;
            }
    for (int k=1;k<m;k++)
        for (int i=0;i<=n;i++) for (int j=0;j<=n-i;j++)
        {
            //x,x
            for (int ii=1;ii<i;ii++) for (int jj=1;jj<j;jj++)
                for (int S=0;S<(1<<k);S++)
                    f[k][i][j][S]=(f[k][i][j][S]+1LL*C[i][ii]*C[j][jj]%p*f[k-1][ii][jj][S]%p*f[k-1][i-ii][j-jj][S])%p;
            //0,x x,0
            for (int ii=1;ii<i;ii++)
                for (int S=0;S<(1<<k);S++)
                    f[k][i][j][S]=(f[k][i][j][S]+1LL*C[i][ii]*pw2[(i-ii)*k]%p*f[k-1][ii][j][S]*2)%p;
            for (int jj=1;jj<j;jj++)
                for (int S=0;S<(1<<k);S++)
                    f[k][i][j][S]=(f[k][i][j][S]+1LL*C[j][jj]*pw2[(j-jj)*k]%p*f[k-1][i][jj][S]*2)%p;
            //0,all all,0
            for (int S=(1<<k);S<(1<<k+1);S++)
                f[k][i][j][S]=(f[k][i][j][S]+f[k-1][i][j][S-(1<<k)]*2)%p;
            for (int S=0;S<(1<<k);S++)
                f[k][i][j][S]=(f[k][i][j][S]+pw2[(i+j)*k]*2)%p;
            //0,0 all,all
            for (int S=0;S<(1<<k);S++)
                f[k][i][j][S]=(f[k][i][j][S]+f[k-1][i][j][S]*2)%p;
        }
    for (int k=0;k<m;k++)
        for (int i=1;i<=n;i++) for (int j=1;j<=n-i;j++)
            for (int S=1;S<(1<<k+1);S++)
                g[k][i][j]=(g[k][i][j]+f[k][i][j][S])%p;
    for (int i=0;i<=n;i++)
        for (int j=0;j<=i;j++)
            h[0][i]=(h[0][i]+(i-j>0&&j>0)*C[i][j])%p;
    for (int k=1;k<m;k++)
        for (int i=0;i<=n;i++)
            for (int j=0;j<=i;j++)
                h[k][i]=(h[k][i]+(1LL*h[k-1][j]*pw2[(i-j)*k]+1LL*h[k-1][i-j]*pw2[j*k]+g[k-1][j][i-j]+(i-j>0&&j>0)*pw2[(i+1)*k])%p*C[i][j]%p)%p;
    printf("%lld",1LL*h[m-1][n]*quick_pow(pw2[n*m],p-2)%p);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值