[2018.07.14 T1] B君的第四题

本文针对一个具体的计数问题进行解析,通过动态规划的方法解决了求解特定图中所有有根生成树权值之和的问题,并给出了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

暂无链接

B君的第四题

【问题描述】

已有的事后必再有,已行的事后必再行。

大家还记得Luogo P3959 [NOIP2017]宝藏 吗?

B君看到很多人用很多弱智的方法水过这个题,感到非常气愤。

这个题目就是把原题改成了一个计数题。

考虑到有些同学没有看过这个题目,我们简述这个题目如下。

输入一个图,我们定义一个有根生成树的权值是

e={x,y}we×max(dx,dy) ∑ e = { x , y } w e × m a x ( d x , d y )

用文字表述就是,枚举每条边,边权乘以两端较深的点的深度。

点的深度是从根节点,走到该点经过的边数。(而不是边权和,和原题定义一样。)

根节点深度为 0 0 ,与根节点相邻的点深度为1,以此类推。

对于一棵树,他的有根生成树的个数是 n n 乘以无根生成树个数,即对于每个无根生成树确定一个根。

输出所有有根生成树的权值之和,对1000000007取模的结果。

【输入格式】

第一行两个整数 n,m n , m 表示点数和边数。

接下来 m m 行,每行三个整数x,y,w表示一条边的两个端点和边权。

【输出格式】

输出所有有根生成树的权值之和,对 1000000007 1000000007 取模的结果。

【输入样例】

4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1

【输出样例】

303

题解

先把出题人的话放在前面:

1.png

%%% % % %

看过这篇博客的朋友应该知道,我就是那个用弱智方法水过的人。。。

虽然很绝望,但是我好像会 60 60 分啊,开个 long long l o n g   l o n g ,唉,稳了稳了。

结果一条链的情况爆 long long l o n g   l o n g ,讲道理无根生成树个数最多 1119 111 9 不会爆啊,好不容易可以多拿一点部分分又滚粗了。。。

正解大概是个装压 dp d p 计数,我们用 sou[d][i][j] s o u [ d ] [ i ] [ j ] 表示到根节点距离为 d d ,当前生成树集合中非叶子节点集合为i,叶子结点为 j j 的生成方案数,ans[d][i][j]表示对应的所有生成树方案权值之和。

每次枚举下一次扩张的集合 k k ,由[d][i][j]转移到 [d+1][i|j][k] [ d + 1 ] [ i | j ] [ k ] ,注意 ijk= i ∩ j ∩ k = ∅ ,所以总复杂度为 O(n4n) O ( n 4 n )

需要预处理 way[i][j] w a y [ i ] [ j ] 表示从 i i 集合扩张到j集合的方案数,以及 add[i][j] a d d [ i ] [ j ] 表示扩张增加的权值。

计算 way[i][j] w a y [ i ] [ j ] 只需要将 i i j看做二分图,把 i,j i , j 之间的边在 j j 部分的度数乘起来即可;计算add[i][j]则是将 j j 集合中每个点连到i的边的权值和乘以这些边的所在的扩展方案数。

还有另一个做法老子不会留给大家思考:
4.png

我还是太菜了。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=(1<<10)+5,N=11,mod=1e9+7;
int n,m,mx,now,x[M<<1],y[M<<1],w[M<<1],i,j,k,d;
ll way[M][M],add[M][M],dw[M],da[M],sou[N][M][M],ans[N][M][M];
void in()
{
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;++i)
    {
        scanf("%d%d%d",&x[i],&y[i],&w[i]);
        --x[i],--y[i],x[i+m]=y[i],y[i+m]=x[i],w[i+m]=w[i];
    }
}
void ac()
{
    m<<=1;mx=1<<n;
    for(i=0;i<mx;++i)
    {
        for(j=0;j<mx;++j)
        {
            if(i&j)continue;
            memset(dw,0,sizeof(dw));memset(da,0,sizeof(da));
            for(k=1;k<=m;++k)if((i>>x[k]&1)&&(j>>y[k]&1))++dw[y[k]],da[y[k]]+=w[k];
            way[i][j]=1;add[i][j]=0;
            for(k=0;k<n;++k)if(j>>k&1)way[i][j]*=dw[k];
            if(!way[i][j])continue;
            for(k=0;k<n;++k)if(j>>k&1)add[i][j]=(add[i][j]+way[i][j]/dw[k]*da[k]%mod)%mod;
        }
    }
    for(i=0;i<n;++i)sou[0][0][1<<i]=1;
    for(d=0;d<n;++d)for(i=0;i<mx;++i)for(j=0;j<mx;++j)
    {
        if((i&j)||0==sou[d][i][j])continue;
        now=(1<<n)-1-i-j;
        for(k=now;;k=(k-1)&now)
        {
            sou[d+1][i|j][k]=(sou[d+1][i|j][k]+sou[d][i][j]*way[j][k]%mod)%mod;
            ans[d+1][i|j][k]=(ans[d+1][i|j][k]+ans[d][i][j]*way[j][k]+sou[d][i][j]*add[j][k]*(d+1)%mod)%mod;
            if(!k)break;
        }
    }
    printf("%lld",ans[n][mx-1][0]);
}
int main(){in();ac();}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值