Never be limited by other people's limited imaginations.|

zyc_xianyu

园龄:3年7个月粉丝:4关注:4

幻想过山车

题目:

 

样例图片:

思路 :

题意是路可以重复走,使得到达n点的时间恰好是p

根据这个性质,我们要想办法凑出这个时间P来。根据路可以重复走的性质,

我们可以找一个环来卡时间,什么样的环最佳呢?

我们先给出答案,以1的最短出边 * 2 构成一个环 在本样例中即为 1 <——> 2 <——> 1

对应代码:

	int u,v,w,i;
	re(n); re(m);
	for(i=1;i<=m;i++)
	{
		re(u); re(v); re(w);
		add(u,v,w);
		add(v,u,w);//无向边建双向 
	}
	d=inf;
	for(i=Head[1];i;i=Next[i]) d=min(d,cost[i]);//找到起点1的出边中最短的 
	re(Q);
	if(d==inf)//如果起点1压根就没有出边,那肯定都不行 
	{
		while(Q--) printf("AKTang!\n");
		return 0;
	}
	d*=2;//把最短的出边*2,变成最小的环 

这样,我们设定点1到点n的一条路径长为l,保证l <= p,使得l 通过环能凑成 p ,(环长度为d =  2 * 最短的出边),那么l % d == p % d。

为什么这个环需要以最短的出边构建呢?会不会有可能这个环长 + l 无法凑出 p?

我们定义数组f[i][j]为从点1到点i的最短距离 % d == j的最短距离。

就是我们把1到点i的(不同种余数)最短距离的距离根据余数存储下来。

所以满足一种余数下的最短距离比p小,那么通过环肯定能凑出p。对应代码:

if(f[n][p%d]<=p) printf("AWaDa!\n");//如果可以通过绕d跑几圈,加上一个路径到n,总长为p,存在总长为p的路径 
else printf("AKTang!\n");//否则不存在 

f数组的更新过程中存在环,所以更新对短路的时候用spfa。对应代码:

void spfa(int mo)
{
	int i,x,disx,y,disy;
	memset(f,0x3f,sizeof(f));//初始化为最大值,以便后期更新 
	q.push(mp(1,0)); vis[1][0]=1; f[1][0]=0;//到点1的距离当然为0,%d也为0,入队 
	while(!q.empty())
	{
		x=q.front().fi; disx=q.front().sc; q.pop();//取出队首,x为当前点,disx为当前到当前点的最小距离%d的值 
		vis[x][disx]=0;//退队 
		for(i=Head[x];i;i=Next[i])
		{
			y=ver[i]; disy=(disx+cost[i])%mo;//y为下一点,disy为由当前点到y算出的1到y的距离%d的值 
			if(f[x][disx]+cost[i]<f[y][disy])//松弛操作 
			{
				f[y][disy]=f[x][disx]+cost[i];
				if(!vis[y][disy])//从1到y,路径长%d为disy的情况,没有再队列中 
				{
					vis[y][disy]=1;
					q.push(mp(y,disy));//入队 
				}
			}
		}
		
	}
}

因为f是二维数组,vis也要做对应的调整。

 总结:

这道题说明了最短路可以附加别的属性,根据具体需要来维护特定的最短路。

回到之前的问题,为什么这个环需要以最短的出边构建呢?

 假设一个最优的环不是最短环,它的长度肯定比最短环长,一定存在一个更短的环,通过绕不同的圈数和走部分环之外的路径,一样能达到同样的效果。

从表达式来看,同样是 p % d,d越小 p % d的范围越小,变量越小,更好。

(这段分析不可信,有错望指出)

 完整代码:

#include<bits/stdc++.h>//d:与起点相连的边上最短边长度*2,即为起点直连的1个最小的环 
//此题主要在于,对于长度为p的1到n的路径,若其存在从1到n的一条路径l,l的长度%d=p%d且l的长度<=p 
using namespace std;
//因为只要有这样的l,p即为先在起点连接的最小的环绕((p-l)/d)圈再走l那条路径 
typedef pair<int,int> PR;
#define mp make_pair
#define fi first
#define sc second

const int N=10010,M=20010,inf=0x3f3f3f3f;
int n,m,Q,p,t,Head[N],ver[M],cost[M],Next[M],tot=1,mi=100,f[N][110],d;
bool vis[N][110];//f[i][j]:由起点1开始,到i点,长度%d==j的路径中,最短的路径的长度 
queue <PR> q;

void re(int &x)
{
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}

inline void add(int x,int y,int z)
{
	ver[++tot]=y; cost[tot]=z; Next[tot]=Head[x]; Head[x]=tot;
}

void spfa(int mo)
{
	int i,x,disx,y,disy;
	memset(f,0x3f,sizeof(f));//初始化为最大值,以便后期更新 
	q.push(mp(1,0)); vis[1][0]=1; f[1][0]=0;//到点1的距离当然为0,%d也为0,入队 
	while(!q.empty())
	{
		x=q.front().fi; disx=q.front().sc; q.pop();//取出队首,x为当前点,disx为当前到当前点的最小距离%d的值 
		vis[x][disx]=0;//退队 
		for(i=Head[x];i;i=Next[i])
		{
			y=ver[i]; disy=(disx+cost[i])%mo;//y为下一点,disy为由当前点到y算出的1到y的距离%d的值 
			if(f[x][disx]+cost[i]<f[y][disy])//松弛操作 
			{
				f[y][disy]=f[x][disx]+cost[i];
				if(!vis[y][disy])//从1到y,路径长%d为disy的情况,没有再队列中 
				{
					vis[y][disy]=1;
					q.push(mp(y,disy));//入队 
				}
			}
		}
		
	}
}

int main()
{
	int u,v,w,i;
	re(n); re(m);
	for(i=1;i<=m;i++)
	{
		re(u); re(v); re(w);
		add(u,v,w);
		add(v,u,w);//无向边建双向 
	}
	d=inf;
	for(i=Head[1];i;i=Next[i]) d=min(d,cost[i]);//找到起点1的出边中最短的 
	re(Q);
	if(d==inf)//如果起点1压根就没有出边,那肯定都不行 
	{
		while(Q--) printf("AKTang!\n");
		return 0;
	}
	d*=2;//把最短的出边*2,变成最小的环 
	spfa(d);//以最小的环为模数进行spfa 
	while(Q--)
	{
		re(p);
		if(f[n][p%d]<=p) printf("AWaDa!\n");//如果可以通过绕d跑几圈,加上一个路径到n,总长为p,存在总长为p的路径 
		else printf("AKTang!\n");//否则不存在 
	}
	return 0;
}

本文作者:zyc_xianyu

本文链接:https://www.cnblogs.com/zychh/p/16726663.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   zyc_xianyu  阅读(17)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起