【校内模拟】祝著节(树上倍增)(最小生成树)

简要题意:

给一张连通无向图,每条边有边权。

你需要把边分成两个集合。

定义一个合法的生成树为分别包含两个集合中至少一条边的树。

请问你有多少种划分方式,使得最小的合法生成树边权和为 X X X

题解:

首先手玩+感性理解+理性证明可以知道满足条件的最小生成树与真实最小生成树最多相差一条边。

设真实最小生成树边权之和为 s u m sum sum

s u m > X sum>X sum>X,显然无解。

否则我们考虑枚举这条强行加上的边,显然扣掉的就是路径最大值,这个可以树上倍增求一下。

s u m = X sum=X sum=X,我们求出有多少条非树边强行加上之后权值不变,那么无法达到目标权值当且仅当所有树边和这些非树边在同一集合里面,用总方案减去即可。

s u m < X sum<X sum<X,我们求出有多少条非树边强行加上之后达到目标,有多少条非树边强行加上之后比目标小,分别设为 o k ok ok b a n ban ban,容易注意到这时候必须强行令MST和 b a n ban ban中的边在同一个集合中, o k ok ok条边至少一条要在另一个集合中。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	template<typename T>T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
	inline ll gl(){return get<ll>();} 
}
using namespace IO;

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int po(int a,int b){
	int r=1;for(;b;b>>=1,a=mul(a,a))
	if(b&1)r=mul(r,a);return r;
}

cs int N=1e5+7,M=N<<1|1;

int n,m;

int bel[N];
int gf(int u){
	while(bel[u]>0&&bel[bel[u]]>0)u=bel[u]=bel[bel[u]];
	return bel[u]>0?bel[u]:u;
}
bool mg(int u,int v){
	while(bel[u]>0&&bel[bel[u]]>0)u=bel[u]=bel[bel[u]];bel[u]>0&&(u=bel[u]);
	while(bel[v]>0&&bel[bel[v]]>0)v=bel[v]=bel[bel[v]];bel[v]>0&&(v=bel[v]);
	if(u==v)return false;bel[u]<bel[v]?(--bel[u],bel[v]=u):(--bel[v],bel[u]=v);
	return true;
}

int Log[N];

int el[N],nxt[M],to[M],w[M],ec;
inline void adde(int u,int v,int vl){
	nxt[++ec]=el[u],el[u]=ec,to[ec]=v,w[ec]=vl;
	nxt[++ec]=el[v],el[v]=ec,to[ec]=u,w[ec]=vl;
}

int d[N];
int f[19][N],mx[19][N];
void dfs(int u,int p,int pre_w){
	d[u]=d[p]+1;f[0][u]=p;mx[0][u]=pre_w;
	for(int re i=0;f[i][u];++i){
		f[i+1][u]=f[i][f[i][u]];
		mx[i+1][u]=std::max(mx[i][f[i][u]],mx[i][u]);
	}
	for(int re e=el[u];e;e=nxt[e])
	if(to[e]!=p)dfs(to[e],u,w[e]);
}

int qmx(int u,int v){int ans=0;
	if(d[u]<d[v])std::swap(u,v);
	for(int re i=Log[d[u]-d[v]];~i;--i){
		if((1<<i)<=(d[u]-d[v])){ans=std::max(ans,mx[i][u]);u=f[i][u];}
	}if(u==v)return ans;
	for(int re i=Log[d[u]];~i;--i){
		if(f[i][u]!=f[i][v]){
			ans=std::max(ans,mx[i][u]);
			ans=std::max(ans,mx[i][v]);
			u=f[i][u],v=f[i][v];
		}
	}return std::max(ans,std::max(mx[0][u],mx[0][v]));
}

int on_mst[M];
struct edge{int u,v,w;}e[M];

void solve(){
	n=gi(),m=gi();ec=0;
	ll sum=0,X=gl();
	memset(el,0,sizeof el);
	memset(bel,0,sizeof bel);
	memset(f,0,sizeof f);
	memset(mx,0,sizeof mx);
	for(int re i=1;i<=m;++i){
		int u=gi(),v=gi(),w=gi();
		e[i]=(edge){u,v,w};
	}std::sort(e+1,e+m+1,[](cs edge &a,cs edge &b){return a.w<b.w;});
	for(int re i=1;i<=m;++i){
		if(on_mst[i]=mg(e[i].u,e[i].v))
		adde(e[i].u,e[i].v,e[i].w),sum+=e[i].w;
	}dfs(1,0,0);
	if(sum>X)cout<<"0\n";
	else if(sum==X){int ok=0;
		for(int re i=1;i<=m;++i)if(!on_mst[i]){
			if(qmx(e[i].u,e[i].v)==e[i].w)++ok;
		}
		cout<<dec(po(2,m),po(2,m-n-ok+2))<<"\n";
	}else {int ok=0,ban=0;
		for(int re i=1;i<=m;++i)if(!on_mst[i]){
			ll now=sum-qmx(e[i].u,e[i].v)+e[i].w;
			if(now==X)++ok;else if(now<X)++ban;
		}
		cout<<dec(po(2,m-n-ban+2),po(2,m-n-ban-ok+2))<<"\n";
	}	
}

signed main(){
#ifdef zxyoi
	freopen("zhuzhu.in","r",stdin);
#endif
	Log[0]=-1;for(int re i=2;i<N;++i)Log[i]=Log[i>>1]+1;
	int T=gi();while(T--)solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值