[HDOJ 4871] Shortest-path tree [树的分治+动态规划]

69 篇文章 0 订阅
26 篇文章 0 订阅

给你一个图,让你构建一个最小路径树,然后问在构建出来的最小路径树中,恰好过k个点的简单路中长度最长的链的长度是多少,同样长度的链有多少条?

题目要求构建最小路径树的方法是,每个点都选取从点1到该点的最短路径中字典序最小的那条路放到树中。

数据范围:点的个数为3*10^5

首先构造最小路径树,我们先进行一遍dijkstra,求出根到每个点的最短路径。然后进行一遍dfs,构建出最小路径树。

然后使用分治算法,每次寻找过重心的恰好过k个点的简单路即可。寻找过重心的恰好过k个点的简单路使用dp,记录dp[i]为只考虑当前这些孩子的情况下,过i个点的一个端点是重心的简单路的最大长度及其次数。

#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <iostream>
#include <algorithm>

using namespace std;

struct Node {
	int fe,v,num;
	bool visited;
};
struct Edge {
	int t,ne,v;
	bool legal;
};

Node a[30001];
Edge b[120000];
int n,m,p,k;

void init() {
	for (int i=1;i<=n;i++) {
		a[i].fe=-1;
		a[i].v=-1;
		a[i].visited=false;
	}
	p=0;
}
void putedge(int x,int y,int z) {
	b[p].t=y;
	b[p].ne=a[x].fe;
	b[p].v=z;
	b[p].legal=false;
	a[x].fe=p++;
}
struct HeapNode {
	int i,v;
	HeapNode() {}
	HeapNode(int ii,int vv) {
		i=ii;v=vv;
	}
	friend bool operator < (const HeapNode &a,const HeapNode &b) {
		if (a.v!=b.v) return a.v>b.v;
		return a.i>b.i;
	}
};
priority_queue <HeapNode> prique;
void getShortestPath(int s) {
	while (!prique.empty()) prique.pop();
	a[s].v=0;
	prique.push(HeapNode(s,0));
	while (!prique.empty()) {
		int i=prique.top().i;
		int v=prique.top().v;
		prique.pop();
		if (a[i].v==v) {
			for (int j=a[i].fe;j!=-1;j=b[j].ne) {
				if (a[b[j].t].v==-1||a[b[j].t].v>v+b[j].v) {
					a[b[j].t].v=v+b[j].v;
					prique.push(HeapNode(b[j].t,a[b[j].t].v));
				}
			}
		}
	}
}
void print() {
	for (int i=1;i<=n;i++) 
		printf("%d %d\n",i,a[i].v);
}
bool cmp(int x,int y) {
	return b[x].t<b[y].t;
}
void buildShortestPathTree(int i) {
	a[i].visited=true;
	vector<int>tmp;
	for (int j=a[i].fe;j!=-1;j=b[j].ne) {
		tmp.push_back(j);
	}
	sort(tmp.begin(),tmp.end(),cmp);
	for (int j=0;j<tmp.size();j++) {
		if (!a[b[tmp[j]].t].visited&&a[b[tmp[j]].t].v==a[i].v+b[tmp[j]].v) {
			b[tmp[j]].legal=true;
			b[tmp[j]^1].legal=true;
			buildShortestPathTree(b[tmp[j]].t);
		}
	}
}
struct AnsNode {
	int len,num;
	void clear() {
		len=num=-1;
	}
	void update(int l,int n=1) {
		if (l>len) {
			len=l;
			num=n;
		} else if (l==len) {
			num+=n;
		}
	}
};
AnsNode ans;
int center,centerV,nn,dpn;
AnsNode dp[30000],curdp[30000];
void getNum(int i) {
	a[i].visited=true;
	a[i].num=1;
	for (int j=a[i].fe;j!=-1;j=b[j].ne) {
		if (b[j].legal&&!a[b[j].t].visited) {
			getNum(b[j].t);
			a[i].num+=a[b[j].t].num;
		}
	}
	a[i].visited=false;
}
void getCenter(int i) {
	int tmp=nn-a[i].num;
	a[i].visited=true;
	for (int j=a[i].fe;j!=-1;j=b[j].ne) {
		if (b[j].legal&&!a[b[j].t].visited) {
			getCenter(b[j].t);
			tmp=max(tmp,a[b[j].t].num);
		}
	}
	if (tmp<centerV) {
		centerV=tmp;
		center=i;
	}
	a[i].visited=false;
}
void getdp(int i,int v,int l) {
	if (l>=k) return;
	curdp[l].update(v);
	if (dp[k-l-1].num!=-1) ans.update(v+dp[k-l-1].len,dp[k-l-1].num);
	a[i].visited=true;
	for (int j=a[i].fe;j!=-1;j=b[j].ne) {
		if (b[j].legal&&!a[b[j].t].visited) {
			getdp(b[j].t,v+b[j].v,l+1);
		}
	}
	a[i].visited=false;
}
void divideAndConquer(int i) {
	getNum(i);
	nn=centerV=a[i].num;
	if (nn<k) return;
	getCenter(i);
	i=center;
	a[i].visited=true;
	for (int t=min(k,nn);t>=0;t--) dp[t].clear();
	dp[0].update(0);
	for (int j=a[i].fe;j!=-1;j=b[j].ne) {
		if (b[j].legal&&!a[b[j].t].visited) {
			for (int t=min(k,a[b[j].t].num);t>=0;t--) curdp[t].clear();
			getdp(b[j].t,b[j].v,1);
			for (int t=min(k,a[b[j].t].num);t>=0;t--) dp[t].update(curdp[t].len,curdp[t].num);
		}
	}
	for (int j=a[i].fe;j!=-1;j=b[j].ne) {
		if (b[j].legal&&!a[b[j].t].visited) {
			divideAndConquer(b[j].t);
		}
	}
}

int main() {
	int i,j,t,x,y,z;
	scanf("%d",&t);
	while (t--) {
		scanf("%d%d%d",&n,&m,&k);
		init();
		for (i=0;i<m;i++) {
			scanf("%d%d%d",&x,&y,&z);
			putedge(x,y,z);
			putedge(y,x,z);
		}
		getShortestPath(1);
		buildShortestPathTree(1);
		for (i=1;i<=n;i++) a[i].visited=false;
		ans.clear();
		divideAndConquer(1);
		printf("%d %d\n",ans.len,ans.num);
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值