初见安~这里是传送门:洛谷P2993 最短路径树问题
题目描述
给一个包含n个点,m条边的无向连通图。从顶点1出发,往其余所有点分别走一次并返回。
往某一个点走时,选择总长度最短的路径走。若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11,路径B为1,3,2,11,路径B字典序较小。注意是序列的字典序的最小,而非路径中节点编号相连的字符串字典序最小)。到达该点后按原路返回,然后往其他点走,直到所有点都走过。
可以知道,经过的边会构成一棵最短路径树。请问,在这棵最短路径树上,最长的包含K个点的简单路径长度为多长?长度为该最长长度的不同路径有多少条?
这里的简单路径是指:对于一个点最多只经过一次的路径。不同路径是指路径两端端点至少有一个不同,点A到点B的路径和点B到点A视为同一条路径。
输入格式
第一行输入三个正整数n,m,K,表示有n个点m条边,要求的路径需要经过K个点。
接下来输入m行,每行三个正整数Ai,Bi,Ci(1<=Ai,Bi<=n,1<=Ci<=10000),表示Ai和Bi间有一条长度为Ci的边。
数据保证输入的是连通的无向图。
输出格式
输出一行两个整数,以一个空格隔开,第一个整数表示包含K个点的路径最长为多长,第二个整数表示这样的不同的最长路径有多少条。
输入
6 6 4
1 2 1
2 3 1
3 4 1
2 5 1
3 6 1
5 6 1
输出
3 4
说明/提示
对于所有数据n<=30000,m<=60000,2<=K<=n。
数据保证最短路径树上至少存在一条长度为K的路径。
题解
题意好像有点儿争议……我再复述一遍:给你一张图,求出一棵最短路径树,并且问你这棵树上包含K个点的最长路径的长度以及在这棵树上以此为长度的路径有多少条。也就是说我们把树求出来了,原图就可以扔了。【迷惑。求法上文附连接。
接下来我们考虑包含K个点的最长路径——也就是包含K-1条边的最长路径以及次数。求最长路径可能有点儿麻烦,但是次数是很好求的——求一棵树上长度为X的路径的出现次数这不就是点分治的板子题嘛!!!那长度呢?我们动态长度吧【大雾】意思就是我们边求数量边更新长度, 如果相同那就累加,更长就更新。
就这样。是个点分治的模板变式。
上代码——
#include<algorithm>//所求:1.包含K个点的最长路径条数 2. 该长度的路径条数 最后的路径数量可能要 >>1 。A->B = B->A
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<cmath>
#define maxn 30005
#define maxm 60005
using namespace std;
typedef long long ll;
int read() {
int x = 0, ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x;
}
//基础设定
int n, m, K;
struct edge {
int to, w, nxt;
edge() {}
edge(int tt, int ww, int nn) {to = tt, w = ww, nxt = nn;}
}e[maxn << 1], e_[maxm << 1];
int head[maxn], k = 0;
void add(int u, int v, int w) {e[k] = edge(v, w, head[u]); head[u] = k++;}
int head_[maxn], k_ = 0;
void add_(int u, int v, int w) {e_[k_] = edge(v, w, head_[u]); head_[u] = k_++;}
//最短路径树
int dis[maxn], fa[maxn], eg[maxn];
priority_queue<pair<int, int> > q;
void dij(edge *e, int *head) {//大致为最短路的板子
memset(dis, 0x3f, sizeof dis); dis[1] = 0;
memset(fa, 0x3f, sizeof fa); fa[1] = 0;
q.push(make_pair(0, 1));
while(q.size()) {
register int u = q.top().second; q.pop();
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to;
if(dis[u] + e[i].w < dis[v]) {
dis[v] = dis[u] + e[i].w; fa[v] = u; eg[v] = e[i].w;
q.push(make_pair(-dis[v], v));
}
else if(dis[u] + e[i].w == dis[v] && u < fa[v]) {//fa记录树上的父节点
fa[v] = u; eg[v] = e[i].w; q.push(make_pair(-dis[v], v));
}
}
}
}
//点分治
int size[maxn], root, part[maxn], max_num, lenth = 0;
bool vis[maxn];
void get_root(int u, int fa) {
size[u] = 1; part[u] = 0;
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to; if(v == fa || vis[v]) continue;
get_root(v, u); size[u] += size[v];
part[u] = max(part[u], size[v]);
}
part[u] = max(part[u], max_num - size[u]);
if(part[u] < part[root]) root = u;
}
struct node {
int pointnum, lenth;
node() {}
node(int pp, int ll) {pointnum = pp, lenth = ll;}
}judge[maxm], ans, len[maxm], d[maxn];
//len和d数组的pointnum是边数……【不能用点数QuQ
void get_dis(int u, int fa) {
if(d[u].pointnum <= K) len[++len[0].pointnum] = d[u];
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to; if(v == fa || vis[v]) continue;
d[v].pointnum = d[u].pointnum + 1; d[v].lenth = d[u].lenth + e[i].w;
get_dis(v, u);
}
}
//judge和ans的pointment是数量统计,lenth是记录的最长长度
void cal(int u) {
len[0].pointnum = 0; get_dis(u, 0);
for(int i = 1; i <= len[0].pointnum; i++) {//和桶judge匹配
register int num = len[i].pointnum, lth = len[i].lenth;
if(num < K - 1) {//需要匹配
register int tmp = K - 1 - num;
if(judge[tmp].lenth + lth > ans.lenth)
ans = node(judge[tmp].pointnum, judge[tmp].lenth + lth);
else if(judge[tmp].lenth + lth == ans.lenth)
ans.pointnum += judge[tmp].pointnum;
}
else if(num == K - 1) {//自成一路
if(lth > ans.lenth) ans = node(1, lth);
else if(lth == ans.lenth) ans.pointnum++;
}
}
for(int i = 1; i <= len[0].pointnum; i++) {//累加进桶
register int num = len[i].pointnum, lth = len[i].lenth;
if(num < K - 1) {
if(lth > judge[num].lenth) judge[num] = node(1, lth);
else if(lth == judge[num].lenth) judge[num].pointnum++;
}
}
}
void solve(int u) {
vis[u] = true; dis[u] = 1;
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to; if(vis[v]) continue;
d[v].pointnum = 1, d[v].lenth = e[i].w;
cal(v);//处理各个子树
}
for(int i = 0; i <= K * 2; i++) judge[i].pointnum = judge[i].lenth = 0;//记得清空
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to; if(vis[v]) continue;
max_num = size[v]; root = 0; get_root(v, u);
solve(root);
}
}
signed main() {
memset(head, -1, sizeof head);
memset(head_, -1, sizeof head_);
n = read(), m = read(), K = read();
for(int u, v, w, i = 1; i <= m; i++) u = read(), v = read(), w = read(), add_(u, v, w), add_(v, u, w);
dij(e_, head_);//因为原图后期不需要,所以加个下划线。
for(int i = 2; i <= n; i++) add(i, fa[i], eg[i]), add(fa[i], i, eg[i]);//存树
max_num = part[0] = n; root = 0;
get_root(1, 0);
solve(root);
printf("%d %d\n", ans.lenth, ans.pointnum);
return 0;
}
这个题思路比较简单,写起来还是……比较有意思吧。
迎评:)
——End——