暂无链接
B君的第四题
【问题描述】
已有的事后必再有,已行的事后必再行。
大家还记得Luogo P3959 [NOIP2017]宝藏 吗?
B君看到很多人用很多弱智的方法水过这个题,感到非常气愤。
这个题目就是把原题改成了一个计数题。
考虑到有些同学没有看过这个题目,我们简述这个题目如下。
输入一个图,我们定义一个有根生成树的权值是
用文字表述就是,枚举每条边,边权乘以两端较深的点的深度。
点的深度是从根节点,走到该点经过的边数。(而不是边权和,和原题定义一样。)
根节点深度为 ,与根节点相邻的点深度为 ,以此类推。
对于一棵树,他的有根生成树的个数是 乘以无根生成树个数,即对于每个无根生成树确定一个根。
输出所有有根生成树的权值之和,对 取模的结果。
【输入格式】
第一行两个整数 表示点数和边数。
接下来 行,每行三个整数 表示一条边的两个端点和边权。
【输出格式】
输出所有有根生成树的权值之和,对 取模的结果。
【输入样例】
4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1
【输出样例】
303
题解
先把出题人的话放在前面:
看过这篇博客的朋友应该知道,我就是那个用弱智方法水过的人。。。
虽然很绝望,但是我好像会 分啊,开个 ,唉,稳了稳了。
结果一条链的情况爆 ,讲道理无根生成树个数最多 不会爆啊,好不容易可以多拿一点部分分又滚粗了。。。
正解大概是个装压 计数,我们用 表示到根节点距离为 ,当前生成树集合中非叶子节点集合为 ,叶子结点为 的生成方案数, 表示对应的所有生成树方案权值之和。
每次枚举下一次扩张的集合 ,由 转移到 ,注意 ,所以总复杂度为 。
需要预处理 表示从 集合扩张到 集合的方案数,以及 表示扩张增加的权值。
计算 只需要将 与 看做二分图,把 之间的边在 部分的度数乘起来即可;计算 则是将 集合中每个点连到 的边的权值和乘以这些边的所在的扩展方案数。
还有另一个做法老子不会留给大家思考:
我还是太菜了。
代码
#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();}