【树的分治】FJOI2014最短路径树问题

【FJOI2014】最短路径树问题

【题目大意】给一个包含n个点,m条边的无向连通图。从顶点1出发,往其余所有点分别走一次并返回。
往某一个点走时,选择总长度最短的路径走。若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11,路径B为1,3,2,11,路径B字典序较小。注意是序列的字典序的最小,而非路径中节点编号相连的字符串字典序最小)。到达该点后按原路返回,然后往其他点走,直到所有点都走过。
可以知道,经过的边会构成一棵最短路径树。请问,在这棵最短路径树上,最长的包含K个点的简单路径长度为多长?以及长度为该最长长度的不同简单路径有多少条?

这里的简单路径是指:对于一个点最多只经过一次的路径。不同路径是指路径两端端点至少有一个不同,点A到点B的路径和点B到点A视为同一条路径。

【题解】今天太晚了以后再写

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define fo(i,a,b) for (int i = a;i <= b;i ++)

using namespace std;
const int maxn = 30005;
int N,M,K,ans,sum,root,size,cnt;
int s[maxn],f[maxn];

struct graph
{
	int a[maxn],b[maxn*4],c[maxn*4],d[maxn*4],tot;
	bool flag[maxn];
	void Insert(int,int,int);
	void SPFA();
	void Findroot(int,int);
	void dfs(int,int,int,int,int);
	void collect(int,int,int,int,int);
	void Work(int,int);
}OLD,NEW;

struct T
{
	int l,n,t;
	inline int ql(int TIME)
	{
		if (TIME != t)
		{
			t = TIME;
			l = -1000000;
			n = 0;
			return 0;
		}
		return l;
	}
};

void graph::Insert(int x,int y,int z)
{
	tot ++;
	b[tot] = y;
	d[tot] = z;
	c[tot] = a[x];
	a[x] = tot;
}

void graph::SPFA()
{
	static int q[maxn*5];
	static bool bz[maxn];
	static int last[maxn],weight[maxn];
	static vector<int> path[maxn];
	static int dist[maxn];
	fo(i,2,N) path[i].push_back(50000);
	memset(dist,127,(N+1)*4);
	int l = 0, r = 1;
	q[1] = 1; dist[1] = 0;
	bz[1] = 1;
	path[1].push_back(1);
	while (l < r)
	{
		int x = q[++l];
		for (int i = a[x];i;i = c[i])
			if (dist[x] + d[i] <= dist[b[i]])
			{
				static vector<int> temp;
				temp = path[x];
				temp.push_back(b[i]);
				if (dist[x] + d[i] < dist[b[i]] || temp < path[b[i]])
				{ 
					path[b[i]] = temp;
					dist[b[i]] = dist[x] + d[i];
					last[b[i]] = x, weight[b[i]] = d[i];
					if (!bz[b[i]])
					{
						bz[b[i]] = 1;
						q[++r] = b[i];
					}
				}
			}
		bz[x] = 0;
	}
	fo(i,2,N)
	{
		NEW.Insert(last[i],i,weight[i]);
		NEW.Insert(i,last[i],weight[i]);
	}
}

void graph::Findroot(int x,int last)
{
	s[x] = 1;
	f[x] = 0;
	for (int i = a[x];i;i = c[i])
		if (b[i] != last && !flag[b[i]])
		{
			Findroot(b[i],x);
			s[x] += s[b[i]];
			f[x] = max(f[x],s[b[i]]);
		}
	f[x] = max(f[x],size-s[x]);
	if (f[x] < f[root]) root = x;
}

T tank[maxn];

inline void update(int x,int _l,int TIME)
{
	if (tank[x].t == TIME)
	{
		if (_l > tank[x].l)
		{
			tank[x].l = _l;
			tank[x].n = 1;
		} else 
		if (_l == tank[x].l)
			tank[x].n ++;
	} else
	{
		tank[x].l = _l;
		tank[x].n = 1;
	}
	tank[x].t = TIME;
}

inline void upd_ans(int l,int cnt)
{
	if (l > ans)
	{
		ans = l;
		sum = cnt;
	} else
	if (l == ans) sum += cnt;
}

void graph::collect(int x,int dep,int len,int last,int TIME)
{
	update(dep,len,TIME);
	for (int i = a[x];i;i = c[i])
		if (b[i] != last && !flag[b[i]])
			collect(b[i],dep+1,len+d[i],x,TIME);
}

void graph::dfs(int x,int dep,int len,int last,int TIME)
{
	if (K-dep > 0)
		upd_ans(tank[K-dep-1].ql(TIME)+len,tank[K-dep-1].n);
	for (int i = a[x];i;i = c[i])
		if (b[i] != last && !flag[b[i]])
			dfs(b[i],dep+1,len+d[i],x,TIME);
}

void graph::Work(int x,int t)
{
	tank[0].t = t;
	tank[0].l = 0;
	tank[0].n = 1;
	for (int i = a[x];i;i = c[i])
		if (!flag[b[i]])
		{
			dfs(b[i],1,d[i],x,t);
			collect(b[i],1,d[i],x,t);
		}
	flag[x] = 1;
	for (int i = a[x];i;i = c[i])
		if (!flag[b[i]])
		{
			f[0] = size = s[b[i]];
			Findroot(b[i],root = 0);
			Work(root,++cnt); 
		}
}

int main()
{
	freopen("short.in","r",stdin);
	freopen("short.out","w",stdout);
	scanf("%d%d%d",&N,&M,&K);
	fo(i,1,M)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		OLD.Insert(x,y,z);
		OLD.Insert(y,x,z);
	}
	OLD.SPFA();
	f[0] = size = N;
	NEW.Findroot(1,root = 0);
	NEW.Work(root,cnt = 1);
	printf("%d %d\n",ans,sum);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值