#2416. 点燃的火焰(flame)

本文探讨了通过燃烧绳子进行计时的算法问题。bx2k发明了一种使用绳子构建树形结构来实现不同计时的方法。文章详细解释了如何通过枚举叶子节点的燃烧状态,并利用深度优先搜索(DFS)来计算所有可能的时间,最终输出前1000种最短的可被统计的时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

bx2k发明了许多有趣的物品。因为他是一个神犇。他决定使用一种古老的计时方法:燃烧绳子。

bx2k有许多长短不一粗细均匀的绳子,如果它的一头被点燃,每秒会沿着绳子的方向烧去一个单位长度。

bx2k把 nnn 根绳子排成了一棵树(nnn 个点 n−1n-1n1 条边的无向连通图)的形状。其中绳子代表边。两根绳子只会在端点处接触。如果一根绳子的某一端开始燃烧,与它接触的绳子也会同时被点着。

bx2k只能在开始时同时点燃树的若干个叶子节点。现在他想知道他可以统计出多少种不同的时间。一种时间可以被统计当且仅当存在一种点燃叶子的方式使得从点燃开始到整棵树燃烧殆尽的时间恰好为该时间。

由于答案可能很多,请你输出方案数对 998244353998244353998244353=7×17×223+1=7×17×223+1=7×17×223+1,一个质数)取模的结果。并输出前 100010001000 小的可被统计的时间。

数据范围

对于100%的数据,2≤n≤500,1≤w≤100002 \le n \le 500,1 \le w \le 100002n500,1w10000

题解

将根转化成有根树

考虑暴力,由于只有叶子结点被烧,所以 2n2^n2n 枚举叶子一开始烧的状态,然后考虑如何快速统计答案

可以转化为每个点什么时候开始被烧,记为 fff 值,初始时只有要烧的叶子是 000 ,其余是 infinfinf ,然后这样的话就可以 dfsdfsdfs 两遍,第一遍是从下往上更新,第二遍是从下往上更新,就可以处理出 fff ,然后每条边 (x,y)(x,y)(x,y) 的答案就是 fx+fy+w2\frac{f_x+f_y+w}{2}2fx+fy+w ,这里可能被烧的地方不在这条边上,不过没有影响的

然后考虑正解,发现被烧的情况可以分为两种,要么是两个被烧叶子之间是最远的,要么一个被烧叶子,一个未被烧叶子是最远的,所以我们可以枚举两个叶子结点,然后我们的目的是尽量使得这个方案成为答案,所以我们将能烧的叶子结点全部烧掉,再用上述方法判断这个方案是否是答案即可

具体讲判断叶子结点能否被烧掉,假设两个叶子结点是 a,ba,ba,b

  1. a,ba,ba,b 都烧:
    考虑叶子结点 ccc ,则如果 dis(a,b)≤dis(b,c)dis(a,b) \le dis(b,c)dis(a,b)dis(b,c) 或者 dis(a,b)≤dis(a,c)dis(a,b) \le dis(a,c)dis(a,b)dis(a,c)ccc 点就可以被烧,可以画图理解一下

  2. aaabbb 不烧:
    考虑叶子结点 ccc ,则如果 dis(a,b)≤dis(b,c)dis(a,b) \le dis(b,c)dis(a,b)dis(b,c)ccc 点就可以被烧,可以画图理解一下

效率:O(n3+n2logn)O(n^3+n^2logn)O(n3+n2logn)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=505,M=N<<1;
int n,hd[N],V[M],nx[M],t,in[N],W[M],h[N],H,rt;
int s[N],d[N][N],fa[N],dp[N],A[N*N],S,q[N],Q;
void add(int u,int v,int w){
	nx[++t]=hd[u];in[v]++;V[hd[u]=t]=v;W[t]=w;
}
void dfs(int u,int fr){
	fa[u]=fr;dp[u]=dp[fr]+1;
	for (int i=hd[u];i;i=nx[i])
		if (V[i]!=fr) s[V[i]]=s[u]+W[i],dfs(V[i],u);
}
int Dfs(int u){
	for (int i=hd[u];i;i=nx[i]) if (V[i]!=fa[u])
		Dfs(V[i]),s[u]=min(s[u],s[V[i]]+W[i]);
	return s[u];
}
int work(){
	for (int i=1;i<=n;i++) s[i]=1e9;
	for (int i=1;i<=Q;i++) s[q[i]]=0,q[i]=0;
	s[rt]=Dfs(rt);int ss=0,l=1,r=1;q[1]=rt;
	while(l<=r){
		int x=q[l++];
		for (int i=hd[x];i;i=nx[i])
			if (V[i]!=fa[x]){
				s[V[i]]=min(s[V[i]],s[x]+W[i]);
				ss=max(s[x]+s[V[i]]+W[i],ss);
				q[++r]=V[i];
			}
	}
	return ss;
}
bool J(int a,int b){
	q[1]=a;q[Q=2]=b;
	for (int i=1;i<=H;i++)
		if (h[i]!=a && h[i]!=b)
			if (d[a][b]<=d[b][h[i]]||d[a][b]<=d[a][h[i]]) q[++Q]=h[i];
	return work()==d[a][b];
}
bool G(int a,int b){
	q[Q=1]=a;
	for (int i=1;i<=H;i++)
		if (h[i]!=a && h[i]!=b)
			if (d[a][b]<=d[b][h[i]]) q[++Q]=h[i];
	return work()==(d[a][b]<<1);
}
int main(){
	scanf("%d",&n);
	for (int u,v,w,i=1;i<n;i++)
		scanf("%d%d%d",&u,&v,&w),
		add(u,v,w),add(v,u,w);
	for (int i=1;i<=n;i++)
		if (in[i]<2) h[++H]=i;
		else rt=i;dfs(rt,0);
	for (int x,y,i=1;i<=n;i++)
		for (int j=i;j<=n;j++){
			x=i,y=j;if (dp[x]<dp[y]) swap(x,y);
			while(dp[x]>dp[y]) x=fa[x];
			while(x!=y) x=fa[x],y=fa[y];
			d[i][j]=d[j][i]=s[i]-s[x]+s[j]-s[x];
		}
	for (int i=1;i<=H;i++)
		for (int j=1;j<=H;j++){
			if (i<j && J(h[i],h[j])) A[++S]=d[h[i]][h[j]];
			if (i!=j && G(h[i],h[j])) A[++S]=(d[h[i]][h[j]]<<1);
		}
	sort(A+1,A+S+1);S=unique(A+1,A+S+1)-A-1;
	printf("%d\n",S);S=min(S,1000);
	for (int i=1;i<=S;i++){
		printf("%d",A[i]>>1);
		if (A[i]&1) putchar('.'),putchar('5');
		putchar(i<S?' ':'\n');
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值