闪烁魔法(C++)

题目背景

Nancy 是一名清华大学的魔法师,她最擅长的魔法是闪烁魔法。

题目描述

清华大学共有 n 个地点,编号从 1至 n。Nancy 的闪烁魔法可以让自己从某个地点瞬间移动到另一个地点。每次使用闪烁魔法都需要消耗一定的魔法值。在不同的两个地点之间使用闪烁魔法进行移动需要消耗的魔法值可能不同。

利用闪烁魔法,Nancy 在清华大学生活得十分自在。然而,好景不长,由于黑恶势力的封印,Nancy 的闪烁魔法不能再肆无忌惮地使用了。具体来说,Nancy 只能在特定的 n−1对地点之间使用闪烁魔法。这些特定的地点对、以及在每对地点对之间使用闪烁魔法需要消耗的魔法值会给出。

巧妙的是,Nancy 发现,虽然黑恶势力对她进行了封印,但是她还是可以从任意一个地点出发,只使用闪烁魔法到达其他所有的地点。

Nancy 决定不向黑恶势力低头。现在,Nancy 要执行一个任务。在清华大学的 n 个地点中有 k 个插有旗帜的地点。而 Nancy 的任务正是访问所有插有旗帜的地点。除此之外,任务还要求 Nancy 在结束时回到她的出发点。(出发点可以任选)

Nancy 知道所有的插有旗帜的地点。由于 Nancy 聪明绝顶,所以她一定会选择魔法值消耗最小的行动方案。

作为 Nancy 的粉丝,Yazid 想知道 Nancy 会消耗多少魔法值。但Yazid 并不知道哪些地点有旗帜。于是他只好退而求其次,假设所有插旗帜的方案是等概率的,尝试求解 Nancy 消耗魔法值的期望值。(即所有可能情况消耗魔法值的平均值)

可惜的是,Yazid 实在是太弱了。你能帮帮他吗?

输入格式

本题包含多组数据,第一行一个正整数 T,表示数据组数。接下来依次描述每组数据,对于每组数据:

第一行 2 个正整数 n, k,分别表示清华大学的地点数目,以及插有旗帜的地点的数目。

接下来n−1 行,每行 3 个正整数 u,v,w,表示一组没有被封印的闪烁魔法为:消耗 w 点魔法值从 u 闪烁至 v(或从 v 闪烁至 u)。

输出格式

对于每组数据,输出一行一个正整数,表示期望消耗的魔法值在模 998244353 意义下的结果。

样例 1 输入

1
3 2
1 2 1
2 3 3

样例 1 输出

332748123

样例 2

见选手目录下的 ex_a1.in 与 ex_a1.ans 。该样例的数据规模与第 10 个测试点相同。

提示

对于分数 U/V ,其在模 P 意义下的值 ans 是指满足 u≡v×ans(modP) 的数。可以证明,如果 P=998244353,则对于任意满足 v 不是 P 的倍数的分数都有 ans 唯一。

此外,根据费马小定理,如果 PP 是质数,aa 不是 PP 的倍数,有 aP−1=a×aP−2≡1(modP)。

子任务

对于 20% 的数据,保证 n ≤ 20。

对于另外 20% 的数据,保证 k=2。

对于另外 20%的数据,保证 k=n。

对于另外 20% 的数据,保证 n≤200。

对于 100% 的数据,保证 1≤k≤n≤100,000,T≤4,w≤10,000。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
#define N 100005
#define mod 998244353
#define ll long long
using namespace std;

int T , n, k, u, v, w, tot, head[N*4], size1[N], vis[N], dist[N];
ll ans, fac[N], inv[N];
struct node
{
	int vet,next,len,u;
}edge[N*4];

void add(int u,int v,int w)
{
	edge[++tot].vet=v;
	edge[tot].next=head[u];
	head[u]=tot;
	edge[tot].len=w;
	edge[tot].u=u;
}

void dfs(int u,int dis)
{
	size1[u]=1;
	vis[u]=false;
	dist[u]=dis;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		int v=edge[i].vet;
		if(vis[v])
		{
			dfs(v,dis+1);
			size1[u]+=size1[v];
		}
	}
}

ll pow(ll a,int b)
{
	ll ans=1,p=a;
	while(b)
	{
		if(b%2==1)ans=(ans*p)%mod;
		p=(p*p)%mod;
		b/=2;
	}
	return ans;
}
ll calc(int n,int m)
{
	if(m>n)return 0;
	return ((fac[n]*inv[n-m])%mod*inv[m])%mod;
}
int main()
{
	freopen("ex_a1.in","r",stdin);
	freopen("ex_a1.ans","w",stdout);
	fac[0]=1;inv[0]=1;
	for(int i=1;i<=N;i++)fac[i]=(fac[i-1]*i)%mod;
	for(int i=1;i<=N;i++)inv[i]=pow(fac[i],mod-2);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		ans=0;tot=0;
		memset(head,-1,sizeof(head));
		for(int i=1;i<n;i++)
		{
			scanf("%d%d%d",&u,&v,&w);
			add(u,v,w);add(v,u,w);
			ans=(ans+w*2)%mod;
		}
		if(n==k)
		{
			printf("%d\n",ans);
			continue;
		}
		memset(vis,true,sizeof(vis));
		dfs(1,0);
		ans=0;
		for(int u=1;u<=n;u++)
		{
			for(int i=head[u];i!=-1;i=edge[i].next)
			{
				int v=edge[i].vet,u1=u;
				if(dist[u1]<dist[v])swap(u1,v);
				ans=(ans+(((calc(n,k)-calc(size1[u1],k)-calc((n-size1[u1]),k))%mod+mod)%mod)*edge[i].len)%mod;
			}
		}
		ll p=calc(n,k);
		printf("%lld\n",(ans*pow(p,mod-2))%mod);
	}
	return 0;
}

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值