Atcoder arc093E

63 篇文章 0 订阅
16 篇文章 0 订阅

考虑先不管染色,任意求出原图的一棵MST,设为 T T T。这个可以用经典的kruskal算法解决。

我们称生成树 T T T的权重和为 w ( T ) w(T) w(T)

w ( T ) > X w(T)>X w(T)>X,显然一定无解。接下来我们假设 w ( T ) ≤ X w(T)\leq X w(T)X

那么对于一个染色方案,若 T T T中同时有两种颜色的边,那它本身就是符合题意的生成树中权重和最小的之一。显然当且仅当 w ( T ) = X w(T)=X w(T)=X时,它对答案有 ( 2 n − 1 − 2 ) ⋅ 2 m − n + 1 (2^{n-1}-2)\cdot 2^{m-n+1} (2n12)2mn+1的贡献( T T T中的边不能全部同色,不在 T T T中的边可以任意染色)。

接下来我们考虑 T T T中只有一种颜色的情况,不妨设 T T T中的边全部染成黑色,白色的情况贡献是相同的。

我们可以证明,这种情况下,若存在符合题意的生成树,那么一定有至少一棵是选择一条不在 T T T中的白色边 ( u , v ) (u,v) (u,v),替换掉 T T T u u u v v v间路径上的最大边权的边得到的。证明是我们可以考虑从 T T T开始,不断替换一条边为不在 T T T中的边,仍然得到一棵生成树。每次替换的过程都不会减小权重和,因此我们不会替换一条黑色边进去,并且也不会替换两条白色边。

进一步地,我们替换第 i i i条边的话,会增大 c o s t ( i ) = w i − max ⁡ { w j ∣ ( u j , v j ) 在 T 中 ( u i , v i ) 两 点 间 路 径 上 } cost(i)=w_i-\max\{w_j|(u_j,v_j)在T中(u_i,v_i)两点间路径上\} cost(i)=wimax{wj(uj,vj)T(ui,vi)}的贡献。因此我们一定会选择 c o s t cost cost最小的边替换。

有了上面的分析就可以计数了。我们把所有不在 T T T中的边按 c o s t cost cost排序( c o s t cost cost相同也强行定下顺序),那么第 i i i条边对答案有贡献当且仅当 w ( T ) + c o s t ( i ) = X w(T)+cost(i)=X w(T)+cost(i)=X,并且它是排序后第一条染成白色的边。假设它按 c o s t cost cost排序后排在 p o s pos pos的位置,会对答案有 2 m − n + 1 − p o s 2^{m-n+1-pos} 2mn+1pos的贡献(只有后面的边能任意染色)。

我们发现复杂度瓶颈在于计算 c o s t cost cost,即计算树上两点间的最大边权。因为范围不大,可以暴力枚举路径上的边查询,单次复杂度 O ( n ) \mathcal O(n) O(n),稍微用倍增优化一下即可单次复杂度 O ( log ⁡ n ) \mathcal O(\log n) O(logn)

时间复杂度视实现为 O ( n m ) \mathcal O(nm) O(nm) O ( m log ⁡ n ) \mathcal O(m\log n) O(mlogn)

#include <bits/stdc++.h>
#define FR first
#define SE second
#define MOD 1000000007

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;

ll pow2[2005];

void pre(int n) {
  pow2[0]=1;
  for(int i=1;i<=n;i++) pow2[i]=pow2[i-1]*2LL%MOD;
}

vector <pr> ee[2005];

int fa[1005][15],maxn[1005][15];
int dep[1005];

void dfs(int x) {
  for(int i=0;i<ee[x].size();i++)
    if (ee[x][i].FR!=fa[x][0]) {
    	int u=ee[x][i].FR;
    	fa[u][0]=x;maxn[u][0]=ee[x][i].SE;
		dep[u]=dep[x]+1;
		for(int j=1;j<15;j++) {
			fa[u][j]=fa[fa[u][j-1]][j-1];
			maxn[u][j]=max(maxn[u][j-1],maxn[fa[u][j-1]][j-1]);
		}
		dfs(u);
	}
}

int query(int x,int y) {
  if (dep[x]<dep[y]) swap(x,y);
  int ans=0,d=dep[x]-dep[y];
  for(int i=0;i<15;i++)
    if ((d>>i)&1) {
    	ans=max(ans,maxn[x][i]);
    	x=fa[x][i];
	}
  if (x==y) return ans;
  for(int i=14;i>=0;i--)
    if (fa[x][i]!=fa[y][i]) {
    	ans=max(ans,maxn[x][i]);
    	ans=max(ans,maxn[y][i]);
    	x=fa[x][i];
    	y=fa[y][i];
	}
  ans=max(ans,maxn[x][0]);
  ans=max(ans,maxn[y][0]);
  return ans;
} 

namespace SETS {

int fa[1005];

void init(int n) {
  for(int i=1;i<=n;i++) fa[i]=i;
}

int find_father(int x) {
  return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}

bool check(int x,int y) {
  x=find_father(x);y=find_father(y);
  return x!=y;
}

void merge(int x,int y) {
  x=find_father(x);y=find_father(y);
  if (x==y) return;
  fa[x]=y;
}

}

struct Edge {
  int s,t,v;
  Edge() {}
  Edge(int a,int b,int c):s(a),t(b),v(c) {}
  bool operator < (const Edge & b) const {return v<b.v;}
};

Edge e[2005];
bool in[2005];

int val[2005];

int main() {
  int n,m;
  ll X;
  scanf("%d%d%lld",&n,&m,&X);
  pre(m);
  for(int i=1;i<=m;i++) {
  	int x,y,z;
  	scanf("%d%d%d",&x,&y,&z);
  	e[i]=Edge(x,y,z);
  }
  sort(e+1,e+m+1);
  SETS::init(n);
  ll s=0;
  for(int i=1;i<=m;i++)
    if (SETS::check(e[i].s,e[i].t)) {
    	int u=e[i].s,v=e[i].t;
    	in[i]=1;
    	s+=e[i].v;
    	SETS::merge(u,v);
    	ee[u].push_back(pr(v,e[i].v));
    	ee[v].push_back(pr(u,e[i].v));
	}
  if (s>X) {
  	puts("0");
  	return 0;
  }
  int ans=0;
  if (s==X) ans=(ans+(pow2[n-1]-2LL+MOD)*pow2[m-n+1])%MOD;
  dfs(1);
  int cnt=0;
  for(int i=1;i<=m;i++)
    if (!in[i]) val[++cnt]=e[i].v-query(e[i].s,e[i].t);
  sort(val+1,val+cnt+1);
  for(int i=1;i<=cnt;i++)
    if (s+val[i]==X) ans=(ans+2LL*pow2[cnt-i])%MOD;
  printf("%d\n",ans);
  return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值