DTOJ #2416. 点燃的火焰(flame)

【题目描述】

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

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

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

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

由于答案可能很多,请你输出方案数对 $998244353$($=7×17×2^{23}+1$,一个质数)取模的结果。并输出前 $1000$ 小的可被统计的时间。

【输入格式】

第一行一个整数 $n$,表示树的节点数。

接下来 $n-1$ 行,每行三个空格隔开的整数 $u,v,w$,表示 $u,v$ 之间有一条长度为 $w$ 的边。

【输出格式】

第一行一个整数 $ans$,表示可以统计出的时间的种数对 $998244353$ 取模的结果。

第二行至多 $1000$ 个空格隔开的整数,表示将所有可以统计的时间从小到大排序后前 $1000$ 小的结果,如果总方案数不及 $1000$,输出所有可被统计的时间。如果输出的时间为小数,保留所有的小数位。

【样例】

样例输入 
4
1 2 1
2 3 2
2 4 1 

样例输出
3
1.5 2 3


样例解释
点燃 $1,3,4$ 号节点,经过 $1.5s$,整棵树被烧光。
点燃 $1,3$ 号节点,经过 $2s$,整棵树被烧光。
点燃 $1$ 号节点,经过 $3s$,整棵树被烧光。

【数据范围与提示】

对于10%的数据,$n le 4 $。

对于20%的数据,$ n le 10 $。

另外的10%的数据,给出的树至多有 $15$ 个叶子节点。

另外的20%的数据,给出的树边权均为 $1$。

对于100%的数据,$2 le n le 500,1 le w le 10000$。

 【题解】

真是道毒瘤分讨+奇技淫巧思维题。。。

首先看看模数,不难想到NTT。在看到输出方案,很容易就能想到NTT。于是我们考虑答案的范围。观察【数据范围与提示】,发现 $n$ 和 $w$ 都非常小。显然答案并不会超过 $n imes w$,答案的种类不会超过 $2 imes n imes w$。所以最大也就 $10000000$ 种,跟 $998244353$ 比一下好像差挺多的。。。这时候显然可以先把它扔了。

先考虑暴力,显然 $2^n$ 枚举子节点点不点燃,然后就不会了考虑答案怎么算。首先对于每个点,肯定是由一条先向上后向下的路径点燃的(可以不向上或不向下)。那么就可以考虑做两边树形 $dp$,第一遍从下往上算最小值,第二遍从上往下算最小值,就可以得出每个点被点燃的时间。接着考虑烧完整棵树。分别考虑每条边 $(u,v)$ 什么时候烧完。分类讨论一下:第一类,从两段往中间烧。这样烧的总时间是 $frac{dis[u]+dis[v]+w(u,v)}{2}$;第二类,从一边往另一边烧,注意到一个性质就是 $dis[u]+w(u,v)=dis[v]$,这样第一类的式子就刚好等于 $dis[v]$,顺便把点的也统计玩了。所以只需要统计边的贡献就好了。

接着考虑优化。我们不难发现,对于每一种答案,只存在两种情况:第一种,从一个叶子节点烧到另一个叶子节点;第二种,从两个叶子节点一起开始烧到中间。这时候就可以对每一对叶子分别考虑,只需要判断是否合法就行了。

第一种情况,我们要让这一对叶子从一边烧过去的时间最长,并且不能被其它叶子先烧了。那么就可以考虑贪心,对每个叶子如果烧到结束叶子比开始叶子烧到结束叶子快,那么这个叶子显然不能点燃。贪心地把所有可以点燃的叶子点燃后,算一遍答案,如果刚好等于开始叶子烧到结束叶子的时间,就是合法的。

第二种情况类似,就是要两段烧到中间之前不能先被烧掉。也同样用贪心地做法即可。但这种情况细节比较多,因为中间可能是个节点也可能是条边,注意分类讨论。

主体部分就是这样。至于一些细节有一些小技巧,例如时间先乘二避免小数之类的,具体实现参考代码。

【代码】

#include<bits/stdc++.h>
inline int read ( void )
{
	int x=0;char ch;bool f=true;
	while ( !isdigit(ch=getchar()) ) if ( ch=='-' ) f=false;
	for ( x=ch^48;isdigit(ch=getchar()); ) x=(x<<1)+(x<<3)+(ch^48);
	return f ? x : -x ;
}
std::vector<int> e[510];
int n,vis[510],dis[510][510],fa[510],d[510];
bool flag[510],ans[10000010];
std::pair<int,int> E[510];
inline void dfs_up ( int u,int fr ) { for ( int v:e[u] ) if ( v!=fr ) dfs_up(v,u),vis[u]=std::min(vis[u],vis[v]+dis[u][v]); }
inline void dfs_down ( int u,int fr ) { for ( int v:e[u] ) if ( v!=fr ) vis[v]=std::min(vis[v],vis[u]+dis[u][v]),dfs_down(v,u); }
inline int getans ( void )
{
	for ( int i=1;i<=n;i++ ) vis[i]=(flag[i]?0:1000000000);
	dfs_up(1,0);dfs_down(1,0);int res=0;
	for ( int i=1;i<n;i++ ) res=std::max(res,vis[E[i].first]+vis[E[i].second]+dis[E[i].first][E[i].second]);
	return res;
}
inline void dfs ( int u ) { for ( int v:e[u] ) if ( v!=fa[u] ) fa[v]=u,dfs(v); }
signed main()
{
	n=read();
	for ( int i=1;i<=n;i++ ) for ( int j=1;j<=n;j++ ) dis[i][j]=(i==j)?0:1000000000;
	for ( int i=1,u,v,w;i<n;i++ ) u=read(),v=read(),w=read(),dis[u][v]=std::min(dis[u][v],w),dis[v][u]=std::min(dis[v][u],w),e[u].push_back(v),e[v].push_back(u),E[i]=std::make_pair(u,v),d[u]++,d[v]++;
	for ( int k=1;k<=n;k++ ) for ( int i=1;i<=n;i++ ) for ( int j=1;j<=n;j++ ) dis[i][j]=std::min(dis[i][j],dis[i][k]+dis[k][j]);
	for ( int u=1;u<n;u++ ) if ( d[u]==1 ) for ( int v=u+1;v<=n;v++ ) if ( d[v]==1 )
	{
		int mid=0;fa[u]=0;dfs(u);
		for ( int x=v;x and dis[v][x]<=dis[x][u];x=fa[x] ) if ( dis[u][x]==dis[x][v] ) { mid=x;break; }
		if ( mid )
		{
			for ( int i=1;i<=n;i++ ) if ( d[i]==1 ) flag[i]=((dis[i][mid]<<1)>=dis[u][v]);
			if ( getans()==dis[u][v] ) ans[dis[u][v]]=true;
		}
		else
		{
			int midx=0,midy=0;
			for ( int x=v;x and dis[v][x]<=dis[x][u];x=fa[x] ) if ( (dis[x][v]<<1)<=dis[u][v] and (dis[fa[x]][u]<<1)<=dis[u][v] ) { midx=x;midy=fa[x];break; }
			for ( int i=1;i<=n;i++ )
				if ( d[i]==1 )
				{
					if ( dis[i][midx]<dis[i][midy] ) flag[i]=(dis[i][midx]>=dis[v][midx]);
					else flag[i]=(dis[i][midy]>=dis[u][midy]);
				}
			if ( getans()==dis[u][v] ) ans[dis[u][v]]=true;
		}
	}
	for ( int u=1;u<=n;u++ ) if ( d[u]==1 ) for ( int v=1;v<=n;v++ ) if ( d[v]==1 and v!=u )
	{
		for ( int i=1;i<=n;i++ ) if ( d[i]==1 ) flag[i]=(dis[i][v]>=dis[u][v]);
		if ( getans()==(dis[u][v]<<1) ) ans[dis[u][v]<<1]=true;
	}
	int tot=0;
	for ( int i=1;i<=10000000;i++ ) if ( ans[i] ) tot++;
	printf("%d
",tot);
	for ( int i=1,cnt=1;i<=10000000 and cnt<=1000;i++ ) if ( ans[i] )
	{
		if ( i&1 ) printf("%.1lf ",i*0.5);
		else printf("%d ",i>>1);
		cnt++;
	}
	return 0;
}

  

原文地址:https://www.cnblogs.com/RenSheYu/p/11305809.html