UVA318 Domino Effect 题解

link

先看题。

0x00 思路

「给定由多米诺骨牌组成的系统,计算最后一个多米诺骨牌在何时何地倒下。当一个关键的多米诺骨牌倒下时,连接到该关键多米诺骨牌的所有多米诺骨牌都开始倒下除了那些已经倒下的多米诺骨牌)。当倒下的多米诺骨牌到达尚未倒下的其他关键多米诺骨牌时,这些关键多米诺骨牌也会倒下并且引起与它们相连的多米诺骨牌开始倒下。多米诺骨牌行可能会从任何一端开始倒下。甚至有可能同一多米诺骨牌行从两端开始倒下,在这种情况下,该行倒下的最后一张多米诺骨牌位于其行两端的关键多米诺骨牌之间。您可以假设行中的多米诺骨牌以均匀的速度倒下(即为 \(1s\))。」

仍然看不懂?试试这个:

「关于多米诺骨牌游戏,有 \(n\) 个关键骨牌,关键骨牌之间都有一行普通骨牌。告诉你关键骨牌之间一行骨牌倒下所需要的时间。开始时推倒编号为 \(1\) 的关键骨牌,问你最后一个骨牌倒下的时间和位置。(两个相邻关键骨牌可以同时倒下,则中间骨牌在两个关键骨牌之后倒下)。」

一开始看到题实际上是没有什么思路的。让我们分析分析样例:

3 3
1 2 5
1 3 5
2 3 5

嘶……这个输入……有点像图啊。那我们把它理解成图试试看?

n/*点数*/ m/*边数*/
u/*起点*/ v/*终点*/ w/*权值*/
//无向边

而我们的问题就成了:寻找权值最大的 点 / 边 即可。

那么由于起点固定(\(1\)),所以只需要打一个单源最短路,点就处理完了。

接下来看边。

由于已知:在这两个关键多米诺骨牌之间可以有一个普通骨牌。

因为我们可以在两个关键骨牌之间把普通骨牌任意摆放,那么我们可以近似理解成:在两个关键骨牌之间有无数个普通骨牌,而这些普通骨牌倒下的时间为这两个关键骨牌之间最后一块普通骨牌倒下的时间。

而我们在这两块关键骨牌之间能找到的最晚倒下的骨牌就一定是 \(\dfrac{u+v+w}{2}\)(把证明放在最后来写),暴力查找即可。

知道这些之后,我们就可以写出一份成熟的代码了。

但是,在这之前……

0x01 注意

请仔细看输出格式以及样例输出!!!!!!

第一,多组输入输出

第二,不要忘了在每组数据后输出一个空行

第三,注意一下:

System #1

The last domino falls after 27.0 seconds, at key domino 2.

System #2

The last domino falls after 7.5 seconds, between key dominoes 2 and 3.

第四,普通骨牌两边的关键骨牌升序输出

血和泪的教训 QAQ

code

点击查看完整代码
#include<bits/stdc++.h>
using namespace std;
int n,m,eat,times,d[100005];
bool b[100005];
typedef struct _dianquan
{
	int to,times;
}E;
bool operator<(const _dianquan& a,const _dianquan& b)
{
	return a.times>b.times;
}
E t1,t2;
int G[505][505];
void bl(int first)//单源最短路
{
	//清空数组+初始化
	memset(d,0x3f,sizeof(d));
	memset(b,0,sizeof(b));
	d[first]=0;
	priority_queue<E>q;
	b[first]=1;
	t1.to=first;
	t1.times=0;
	q.push(t1);
    
    //开始松弛操作
	int i;
	while(!q.empty())
	{
		int now=q.top().to;
		q.pop();
		b[now]=0;
		for(i=1;i<=n;i++)
		{
			if(G[now][i]<G[0][0]&&d[i]>d[now]+G[now][i])
			{
				d[i]=d[now]+G[now][i];
				if(!b[i])
				{
					b[i]=1;
					t1.to=i;
					t1.times=G[now][i];
					q.push(t1);
				}
			}
		}
	}
}
int main()
{
	int i,j,first,t=0,u,v,w;
	while(scanf("%d %d",&n,&m)!=EOF&&n)//多组输入输出
	{
		t++;
		memset(G,0x3f,sizeof(G));
		for(i=1;i<=n;i++)
			G[i][i]=0;
		for(i=0;i<m;i++)
		{
			scanf("%d %d %d",&u,&v,&w);
			G[u][v]=G[v][u]=w;
		}
		bl(1);
		double keymax=0;
		int keywhere=1;
		for(i=1;i<=n;i++)//最晚落下的关键骨牌
		{
			if(d[i]>keymax)
			{
				keymax=d[i];
				keywhere=i;
			}
		}
		double unkeymax=0;
		int unkey_little=0,unkey_bigger=0;
		for(i=1;i<=n;i++)//最晚落下的普通骨牌
		{
			for(j=i+1;j<=n;j++)
			{
				if((d[i]+d[j]+G[i][j])/2.0>unkeymax&&G[i][j]<G[0][0])
				{
					unkeymax=(d[i]+d[j]+G[i][j])/2.0;
					unkey_little=i;
					unkey_bigger=j;
				}
			}
		}
		printf("System #%d\n",t);
		if(keymax>=unkeymax)//至于为什么是>=,详见后面证明的情况2
			printf("The last domino falls after %.1lf seconds, at key domino %d.\n\n",keymax,keywhere);
		else
			printf("The last domino falls after %.1lf seconds, between key dominoes %d and %d.\n\n",unkeymax,unkey_little,unkey_bigger);
	}
	return 0;
}

0x02 证明

对于任一条边来说只有两种情况。

  1. 这条边无法使其左右两边任何一个关键骨牌倒下(即左右两边的关键骨牌让这条边上的骨牌倒下)。

让我们看一下模式图:

众所周知,多米诺骨牌只要一碰就倒,这也是为什么用其做出的艺术品十分短命。因此在长为 \(w\) 的线段上左端点和右端点相遇的点最后下落。(令 \(u\le v\))而这个点掉落的时间即为

\[v+[w-(v-u)\times 1]\div 2=v+\dfrac{w-v+u}{2}=\dfrac{u+v+w}{2} \]

得证。
2. 当某一边的关键骨牌倒下后,另一个关键骨牌会因这条边的存在而倒下。
同样看一下(假设 \(u\) 先倒):

那么我们可以得到 \(v=u+w\),而在此情况下 \(\dfrac{u+v+w}{2}=\dfrac{2(u+w)}{2}=u+w=v\),也即是此普通骨牌在关键骨牌处,所以为了防止这样的错误发生,我们可以优先选择关键骨牌(即代码中的 if(keymax>=unkeymax))然后才是普通骨牌。

posted @ 2022-01-23 17:11  cqbzcky  阅读(52)  评论(0)    收藏  举报