Tree Ext
这道题相当于把3道题合了起来。
要求修复的边中恰好有 k 条白边:
五颜六色的幻想乡(附拉格朗日插值法求多项式系数 )
+
bzoj2654 tree(WQS二分 新科技get)
是最小生成树计数而非生成树计数:
BZOJ1016」[JSOI2008] 最小生成树计数
具体可以看看这篇博客,代码中的注释也解释得较清楚
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int N=105;
const int M=10005;
int n,m,k,l,r;
int tot,fa[N],pa[N],id[N],num[N];
int x[N],y[N],res[N],coef[N],ans[N];
struct edge{int u,v,w,c;
}e[M];
bool cmp(edge a,edge b){if(a.w==b.w) return a.c<b.c;return a.w<b.w;
}
int find(int u){if(fa[u]==u) return u;return fa[u]=find(fa[u]);
}
int get(int u){if(pa[u]==u) return u;return pa[u]=get(pa[u]);
}
int power(int a,int b){a%=mod;int res=1;while(b){if(b&1) res=res*a%mod;b>>=1;a=a*a%mod;}return res;
}
bool check(int mid){for(int i=1;i<=m;i++)if(!e[i].c) e[i].w+=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].w-=mid;return cnt>=k;
}
int solve(int l,int r,int x){//矩阵树定理:加入这批边中的部分边,构造已有连通块(看成点)的生成树 static int mat[N][N];memset(mat,0,sizeof(mat));for(int i=l;i<=r;i++){int u=id[e[i].u],v=id[e[i].v];if(u==v) continue;if(e[i].c)mat[u][u]++,mat[v][v]++,mat[u][v]--,mat[v][u]--;else//白边权值为x mat[u][u]+=x,mat[v][v]+=x,mat[u][v]-=x,mat[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;mat[i][i]++,mat[i-1][i-1]++,mat[i][i-1]--,mat[i-1][i]--;}}for(int i=1;i<=tot;i++)for(int j=1;j<=tot;j++)mat[i][j]=(mat[i][j]%mod+mod)%mod;int res=1;for(int i=1;i<tot;i++){for(int j=i+1;j<tot;j++){if(mat[j][i]){int t=mat[i][i]*power(mat[j][i],mod-2)%mod,tmp;for(int k=i;k<tot;k++){tmp=(mat[i][k]-mat[j][k]*t%mod+mod)%mod;mat[i][k]=mat[j][k];mat[j][k]=tmp;}res=-res;}}}if(res==-1) res+=mod;for(int i=1;i<tot;i++) res=res*mat[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]*power(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].w,&e[i].c);l=-1e9-1,r=1e9+1;//二分找出一个合适的值x,使得所有白边边权均加上x后最小生成树中恰有k条白边 while(l<r){int mid=(l+r+1)/2;if(check(mid)) l=mid;else r=mid-1;}for(int i=1;i<=m;i++)if(!e[i].c) e[i].w+=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[l].w==e[r+1].w) r++;bool flag=false;for(int i=l;i<=r;i++){if(find(e[i].u)!=find(e[i].v)){flag=true;break;}}if(!flag) continue;memset(id,0,sizeof(id));tot=0;//注意fa没有重置,即之前加的边构成的连通块还在 for(int i=l;i<=r;i++){//考虑一批有相同权值的边 int u=find(e[i].u),v=find(e[i].v);e[i].u=u;e[i].v=v;//缩点 if(u!=v){if(!id[u]){id[u]=++tot;num[tot]=u;//这一批边关联的连通块数量+1 }if(!id[v]){id[v]=++tot;num[tot]=v;//这一批边关联的连通块数量+1 }}}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,基尔霍夫矩阵的行列式为关于x的多项式 //代入不同的x,分别求出对应的y,通过插值求出多项式的各项系数 x[i]=i+1;y[i]=solve(l,r,i+1);}lagerange();memset(coef,0,sizeof(coef));for(int i=0;i<n;i++){//乘法原理,把这一次加边的结果与之前的乘起来 for(int j=0;i+j<n;j++){coef[i+j]+=ans[i]*res[j]%mod;coef[i+j]%=mod;}}memcpy(ans,coef,sizeof(coef));}for(int i=2;i<=n;i++){if(find(i)!=find(1)){puts("0");return 0;}}printf("%lld\n",ans[k]);return 0;
}