【XSY3163】Tree Ext【二分】【最小生成树】【矩阵树定理】【拉格朗日插值】
给一张 n 个点 m 条边的无向连通图,每条边是黑边或白边 ,有边权。问有多少棵恰好有k条白边,且在此前提下边权和最小的生成树。mod 1e9+7。
首先看一看这道题。
ACM Live Archieve 7138
这里有一个无比良心的题解。
这里写链接内容
膜拜这位不知名的ACM大神!
由于发现博主已经两年多没更新了,这个网站可能会隔,蒟蒻截了张图。。。如果侵权,马上删除。
对于恰好k条白边的要求,我们可以二分一个权,给每条白边加上这个权,再最小生成树,注意相同边权白边排前面,如果最小生成树中白边数量>=k判定为可行,否则判定为不可行。
这不就是bzoj2654吗?
最后这题要求的是最小生成树计数,不是生成树计数。不过我们可以通过乘法原理和分段的思想转化为多次生成树计数。这不就是bzoj1016吗?
因为某些玄学bug一直WA30分QAQ
注意可能相同边权的边全部连了之后,各个联通块仍然不连通,所以要提前在他们之间连一些黑边,不影响答案。
题解链接
其实是把三道题强行拼在一起qwq
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cassert>
#define int long long
using namespace std;
const int N=105,M=10005,mod=1000000007;
int n,m,k,l,r,mid,tot,fa[N],pa[N],res[N],ans[N],tmp[N],id[N],num[N],x[N],y[N];
struct edge{
int u,v,d,c;
}e[M];
bool cmp(edge a,edge b){
return a.d==b.d?a.c<b.c:a.d<b.d;
}
int find(int u){
return u==fa[u]?u:fa[u]=find(fa[u]);
}
int get(int u){
return u==pa[u]?u:pa[u]=get(pa[u]);
}
bool check(){
for(int i=1;i<=m;i++){
if(!e[i].c){
e[i].d+=mid;
}
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++){
fa[i]=i;
}
int cnt=0;
for(int i=1,j=0;j<n-1;i++){
int u=find(e[i].u),v=find(e[i].v);
if(u!=v){
fa[v]=u;
j++;
if(!e[i].c){
cnt++;
}
}
}
for(int i=1;i<=m;i++){
if(!e[i].c){
e[i].d-=mid;
}
}
return cnt>=k;
}
int fastpow(int a,int x){
a%=mod;
int res=1;
while(x){
if(x&1){
res=res*a%mod;
}
x>>=1;
a=a*a%mod;
}
return res;
}
int solve(int l,int r,int x){
static int a[N][N];
memset(a,0,sizeof(a));
for(int i=l;i<=r;i++){
int u=id[find(e[i].u)],v=id[find(e[i].v)];
if(u==v){
continue;
}
if(e[i].c){
a[u][u]++;
a[v][v]++;
a[u][v]--;
a[v][u]--;
}else{
a[u][u]+=x;
a[v][v]+=x;
a[u][v]-=x;
a[v][u]-=x;
}
}
memcpy(pa,fa,sizeof(fa));
for(int i=2;i<=tot;i++){
int u=get(num[i]),v=get(num[i-1]);
if(u!=v){
pa[v]=u;
a[i][i]++;
a[i-1][i-1]++;
a[i][i-1]--;
a[i-1][i]--;
}
}
for(int i=1;i<=tot;i++){
for(int j=1;j<=tot;j++){
a[i][j]=(a[i][j]%mod+mod)%mod;
}
}
int res=1;
for(int i=1;i<tot;i++){
for(int j=i+1;j<tot;j++){
if(a[j][i]){
int t=a[i][i]*fastpow(a[j][i],mod-2)%mod,tmp;
for(int k=i;k<tot;k++){
tmp=(a[i][k]-a[j][k]*t%mod+mod)%mod;
a[i][k]=a[j][k];
a[j][k]=tmp;
}
res=-res;
}
}
}
if(res==-1){
res+=mod;
}
for(int i=1;i<tot;i++){
res=res*a[i][i]%mod;
}
return res;
}
void lagerange(){
static int a[N],b[N],c[N];
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(res,0,sizeof(res));
a[0]=1;
for(int i=0;i<=tot;i++){
b[0]=0;
for(int j=1;j<=i+1;j++){
b[j]=a[j-1];
}
for(int j=0;j<=i;j++){
b[j]=(b[j]-x[i]*a[j]%mod+mod)%mod;
}
for(int j=0;j<=i+1;j++){
a[j]=b[j];
}
}
for(int i=0;i<=tot;i++){
for(int j=0;j<=tot+1;j++){
b[j]=a[j];
}
for(int j=tot+1;j>=1;j--){
c[j-1]=b[j];
b[j-1]=(b[j-1]+1LL*b[j]*x[i]%mod)%mod;
}
int tmp=1;
for(int j=0;j<=tot;j++){
if(j!=i){
tmp=tmp*(x[i]-x[j]+mod)%mod;
}
}
tmp=y[i]*fastpow(tmp,mod-2)%mod;
for(int j=0;j<=tot;j++){
c[j]=c[j]*tmp%mod;
res[j]=(res[j]+c[j])%mod;
}
}
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=m;i++){
scanf("%lld%lld%lld%lld",&e[i].u,&e[i].v,&e[i].d,&e[i].c);
}
l=-1e9-1,r=1e9+1;
while(l<r){
mid=(l+r+1)/2;
if(check()){
l=mid;
}else{
r=mid-1;
}
}
for(int i=1;i<=m;i++){
if(!e[i].c){
e[i].d+=l;
}
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++){
fa[i]=i;
}
ans[0]=1;
for(l=1;l<=m;l=r+1){
r=l;
while(r<m&&e[r+1].d==e[l].d){
r++;
}
memset(id,0,sizeof(id));
tot=0;
for(int i=l;i<=r;i++){
int u=find(e[i].u),v=find(e[i].v);
if(u!=v){
if(!id[u]){
id[u]=++tot;
num[tot]=u;
}
if(!id[v]){
id[v]=++tot;
num[tot]=v;
}
}
}
if(!tot||tot==1){
continue;
}
memcpy(pa,fa,sizeof(fa));
for(int i=l;i<=r;i++){
int u=find(e[i].u),v=find(e[i].v);
if(u!=v){
fa[v]=u;
}
}
for(int i=0;i<=tot;i++){
x[i]=i+1;
y[i]=solve(l,r,i+1);
}
lagerange();
memset(tmp,0,sizeof(tmp));
for(int i=0;i<n;i++){
for(int j=0;i+j<n;j++){
tmp[i+j]+=ans[i]*res[j]%mod;
tmp[i+j]%=mod;
}
}
memcpy(ans,tmp,sizeof(tmp));
}
for(int i=2;i<=n;i++){
if(find(i)!=find(1)){
puts("0");
return 0;
}
}
printf("%lld\n",ans[k]);
return 0;
}