FJOI2014最短路径树问题

Description

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

Input

第一行输入三个正整数n,m,K,表示有n个点m条边,要求的路径需要经过K个点。接下来输入m行,每行三个正整数Ai,Bi,Ci(1<=Ai,Bi<=n,1<=Ci<=10000),表示Ai和Bi间有一条长度为Ci的边。数据保证输入的是连通的无向图。

Output

输出一行两个整数,以一个空格隔开,第一个整数表示包含K个点的路径最长为多长,第二个整数表示这样的不同的最长路径有多少条。


题目大意大概就是要根据规定的一些条件建树,然后要求出2个问题,注意第二问长度为该长度的路径也必须包含K个点。

建图的话,首先跑一遍最短路,然后对于一个点u,从小到大枚举与其相邻每个点,若是该点在最短路图上,则建边树。

然后进行树分治,首先我们开一个数组a[i]保存经过i条边的路径的最大长度,用b[i][j]表示经过i条边路径长度为j的路径条数,然后对于一个点的每棵子树单独处理,现在我们考虑合并的情况,假设我们已知前i-1棵子树的信息,然后我们搜索第i棵子树,用dis[]表示经过的边数,用dist表示经过的路径长度,若是存在a[K - dis[u] - 1],则判断大小,更新,在保证路径长度最长的情况下更新边数。由于map数组b速度非常慢(在windows下最慢的点跑了1.40s),我们可以用一个权值线段树来优化,时间复杂度O(n log ^ 2(n)).

(当然我的做法是偏复杂的,存在O(n log n)的做法,请读者自行思考)

附上代码:

#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXX = 60005;
const int INF = 300000005;
const int MAXN = 6000005;
int first[MAXX], next[MAXX << 1], go[MAXX << 1], way[MAXX << 1], t, ans, ans1;
int first1[MAXX], next1[MAXX << 1], go1[MAXX << 1], way1[MAXX << 1], a[MAXX];
int n, i, j, k, l, m, K, root, sroot, size[MAXX];
int dis[MAXX], q[MAXX << 5], w, dist[MAXX], tot;
int sum[MAXN], lc[MAXN], rc[MAXN], len, rt[MAXX];
bool e[MAXX], vis[MAXX];
struct sb{
	int x, y, z;
};
sb sta[MAXX << 1];
inline bool rule(const sb &a, const sb &b)
{
	return (a.x < b.x || (a.x == b.x && a.y > b.y));
}
inline int get()
{
	char c;
	while ((c = getchar()) < 48 || c > 57);
	int res = c - 48;
	while ((c = getchar()) >= 48 && c <= 57)
	res = res * 10 + c - 48;
	return res;
}
inline void add1(const int &x, const int &y, const int &z)
{
	next1[++t] = first1[x]; first1[x] = t; go1[t] = y; way1[t] = z;
}
inline void add(const int &x, const int &y, const int &z)
{
	next[++t] = first[x]; first[x] = t; go[t] = y; way[t] = z;
	next[++t] = first[y]; first[y] = t; go[t] = x; way[t] = z;
}
inline int MAX(const int &x, const int &y)
{
	if (x > y) return x;
	else return y;
}
inline void bfs()
{
	for(int i = 1; i <= n; i ++)
		dis[i] = 707406378;
	dis[q[e[w = 1] = 1] = 1] = 0;
	for(int lpf = 1; lpf <= w; lpf ++)
	{
		int k = q[lpf];
		for(int i = first1[k]; i; i = next1[i])
		if (dis[go1[i]] > dis[k] + way1[i])
		{
			dis[go1[i]] = dis[k] + way1[i];
			if (!e[go1[i]])
			{
				e[go1[i]] = 1;
				q[++w] = go1[i];
				if (dis[q[w]] < dis[q[lpf + 1]]) swap(q[w], q[lpf + 1]);
			}
		}
		e[k] = 0;
	}
}
inline void dfs1(const int &now)
{
	vis[now] = 1;
	for(int i = first1[now]; i; i = next1[i])
	{
		int u = go1[i], v = way1[i];
		if (dis[u] == dis[now] + v && !vis[u])
		{
			add(now, u, v);
			dfs1(u);
		}
	}
}
inline void insert(int &k, const int &p, const int &q, const int &w, int ww)
{
	if (!k) k = ++len;
	if (p == q && p == w)
	{
		sum[k] += ww;
		return;
	}
	int mid = (p + q) >> 1;
	if (mid >= w) insert(lc[k], p, mid, w, ww);
	if (mid < w) insert(rc[k], mid + 1, q, w, ww);
}
inline void find(const int &k, const int &p, const int &q, const int &w)
{
	if (p == q && p == w)
	{
		tot = sum[k];
		return;
	}
	int mid = (p + q) >> 1;
	if (mid >= w) find(lc[k], p, mid, w);
	if (mid < w) find(rc[k], mid + 1, q, w);
}
inline void getroot(const int &now, const int &fa, int p)
{
	size[now] = 1;
	int num = 0;
	for(int i = first[now]; i; i = next[i])
		if (go[i] != fa && !vis[go[i]]) getroot(go[i], now, p), size[now] += size[go[i]], num = MAX(num, size[go[i]]);
	if (p - size[now] > num) num = p - size[now];
	if (num < sroot) sroot = num, root = now;
}
inline void getdis(const int &now, const int &fa)
{
	if (dis[now] >= K) return;
	a[dis[now]] = MAX(a[dis[now]], dist[now]);
	insert(rt[dis[now]], 0, INF, dist[now], 1);
	for(int i = first[now]; i; i = next[i])
	if (go[i] != fa && !vis[go[i]]) getdis(go[i], now);
}
inline void getans(const int &now, const int &fa)
{
	if (dis[now] >= K) return;
	if (dis[now] == K - 1 && dist[now] > ans) ans = dist[now], ans1 = 1;
	else if (dis[now] == K - 1 && dist[now] == ans) ans1++;
	if (K - dis[now] - 1 > 0 && a[K - dis[now] - 1] && a[K - dis[now] - 1] + dist[now] > ans)
	{
		ans = a[K - dis[now] - 1] + dist[now];
		tot = 0;
		find(rt[K - dis[now] - 1], 0, INF, ans - dist[now]);
		ans1 = tot;
	}	
	else if (K - dis[now] - 1 > 0 && a[K - dis[now] - 1] && ans - dist[now] > 0)
	{
		tot = 0;
		find(rt[K - dis[now] - 1], 0, INF, ans - dist[now]);
		ans1 += tot;
	}
	for(int i = first[now]; i; i = next[i])
	if (go[i] != fa && !vis[go[i]])
	{
		dis[go[i]] = dis[now] + 1;
		dist[go[i]] = dist[now] + way[i];
		getans(go[i], now);
	}
}
inline void delet(const int &now, const int &fa)
{
	if (dis[now] >= K) return;
	a[dis[now]] = 0;
	insert(rt[dis[now]], 0, INF, dist[now], -1);
	for(int i = first[now]; i; i = next[i])
	if (go[i] != fa && !vis[go[i]]) delet(go[i], now);
}
inline void solve(const int &now)
{
	vis[now] = 1;
	for(int i = first[now]; i; i = next[i])
	if (!vis[go[i]])
	{
		int v = go[i];
		dis[v] = 1;
		dist[v] = way[i];
		getans(v, now);
		getdis(v, now);
	}
	for(int i = first[now]; i; i = next[i])
	if (!vis[go[i]]) delet(go[i], now);
	for(int i = first[now]; i; i = next[i])
	if (!vis[go[i]])
	{
		sroot = MAXX;
		getroot(go[i], now, size[go[i]]);
		solve(root);
	}
}
int main()
{
	freopen("data.in", "r", stdin);
	freopen("data.out", "w", stdout);
	n = get(); m = get(); K = get();
	for(i = 1; i <= m; i ++)
	{
		sta[i].x = get(); sta[i].y = get(); sta[i].z = get();
		sta[i + m].y = sta[i].x; sta[i + m].x = sta[i].y; sta[i + m].z = sta[i].z;
	}
	sort(sta + 1, sta + 1 + m + m, rule);
	for(i = 1; i <= (m << 1); i ++)
		add1(sta[i].x, sta[i].y, sta[i].z);
	t = 0;
	bfs();
	dfs1(1);
	for(i = 1; i <= n; i ++)
		vis[i] = dis[i] = 0;
	sroot = MAXX;
	getroot(1, 0, n);
	solve(root);
	cout << ans << " " << ans1 << endl;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值