[FJOI2014]最短路径树问题 题解

Preface

,,,

这道题搞了一个晚上

最开始对题面是有疑惑的,以为第二问是不限制节点个数的

瞎搞过后看题解,发现原来理解错了、、、

看了题解代码,打完,调了一阵,发现题解是错了???

woc,题解并没有满足字典序的要求、、

😕😕😕 玩 nm

Statement

P2993 最短路径树问题 - 洛谷

Solve

最后,终究决定自己搞一手。

大致题意:给定一个无向连通图,先求出一颗最短路径树,满足经过的顶点序列取字典序最小的。

求包含 \(k\) 个点的最长路,求包含 \(k\) 个点的最长路又几条

其实这个很明显了,我们肯定需要一个最短路径树 \((SPT)\) ,考虑后面的树上路径计数问题,我们可以自然想到淀粉质

这里最有讲究的其实是在于这个字典序的限制

大家以前也许见过让 \(SPT\) 边权和最小之类的问题,但这里显然不同

这个字典序显然需要和我们之后的淀粉质求解相配合,所以我们先考虑如何淀粉质

求到中心,遍历每个儿子,我们可以把一条路径认为是 \(前面某个子树\to rt+rt\to 当前子树\)

我们设 \(len[i]\) 表示与当前 \(rt\)\(i\) 个点的最长路,\(cnt[i]\) 表示对应的方案数

然后感觉就可以淀粉质爆 \(++\) 了(感觉代码比文字更好懂)

bool cmax(int &a,int b){return a<b?a=b,1:0;}
void calc(int u,int fath,int dep,int dis){
	if(cmax(ans,dis+len[k-dep]))num=cnt[k-dep];//更长的路径
	else if(ans==dis+len[k-dep])num+=cnt[k-dep];//一样长,方案增加
	for(int e=head[u],v;e;e=edge[e].nex)
		if(!vis[v=edge[e].to]&&(v^fath))
    		calc(v,u,dep+1,dis+edge[e].dis);
}
void update(int u,int fath,int dep,int dis){
	if(cmax(len[dep+1],dis))cnt[dep+1]=1;
	else if(len[dep+1]==dis)cnt[dep+1]++;
	for(int e=head[u],v;e;e=edge[e].nex)
		if(!vis[v=edge[e].to]&&(v^fath))
            update(v,u,dep+1,dis+edge[e].dis);
}
for(int e=head[u],v;e;e=edge[e].nex)
    if(!vis[v=edge[e].to])
		calc(v,0,1,edge[e].dis),
		update(v,0,1,edge[e].dis);

发现当前子树贡献是由前面子树而来,那么,我们只要保证前面子树节点字典序尽量靠前就好

考虑在 \((vector)\)​ 存图的时候,我们就把每个点 \(u\)​ 的出边 \((u,v)\)​ 按照 \(v\)​ 从小到大排序,然后跑 \(dijkstra\)

显然,连边条件是 \(dis[v]==dis[u]+edge[e].dis\)

因为我们排了序,顺次枚举儿子,判断连接即可

void build(int u){
	vis[u]=1;
	for(pair<int,int>v:Edge[u])
		if(dis[v.fi]==dis[u]+v.se&&!vis[v.fi])
			addedge(u,v.fi,v.se),build(v.fi);
}

Code

那个 \(define\)​ 的内容可能稍显毒瘤,主要是懒得写

#include<bits/stdc++.h>
#define scan for(int e=head[u],v;e;e=edge[e].nex)
#define pd1 if(!vis[v=edge[e].to]&&(v^fath))
#define pd2 if(!vis[v=edge[e].to])
#define re if(k==dep)return
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int N = 3e4+5;
const int inf = 1e9+7;

struct Edge{
	int nex,to,dis;
}edge[N<<2];
int head[N],dis[N];
int siz[N],mxp[N],len[N],cnt[N];
int n,m,k,rt,elen,ans=-1,num,total;
vector<pii >Edge[N];
bool vis[N];

bool cmin(int &a,int b){return a>b?a=b,1:0;}
bool cmax(int &a,int b){return a<b?a=b,1:0;}
void addedge(int u,int v,int w){
	edge[++elen]={head[u],v,w},head[u]=elen;
	edge[++elen]={head[v],u,w},head[v]=elen;
}
void dijkstra(){
	priority_queue<pii>q;
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0,q.push(mp(0,1));
	while(q.size()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;vis[u]=1;
		for(pair<int,int>v:Edge[u])
			if(cmin(dis[v.fi],dis[u]+v.se))
				q.push(mp(-dis[v.fi],v.fi));
	}
}
void build(int u){
	vis[u]=1;
	for(pair<int,int>v:Edge[u])
		if(dis[v.fi]==dis[u]+v.se&&!vis[v.fi])
			addedge(u,v.fi,v.se),build(v.fi);
}

void getroot(int u,int fath){
	siz[u]=1,mxp[u]=0;
	scan pd1 getroot(v,u),
		siz[u]+=siz[v],cmax(mxp[u],siz[v]);
	cmax(mxp[u],total-siz[u]);
	if(mxp[u]<mxp[rt])rt=u;
}
void clear(int u,int fath,int dep){
	re;
	len[dep+1]=len[k-dep]=-inf;
	cnt[dep+1]=cnt[k-dep]=0;
	scan pd1 clear(v,u,dep+1);	
}
void calc(int u,int fath,int dep,int dis){
	re;
	if(cmax(ans,dis+len[k-dep]))num=cnt[k-dep];
	else if(ans==dis+len[k-dep])num+=cnt[k-dep];
	scan pd1 calc(v,u,dep+1,dis+edge[e].dis);
}
void update(int u,int fath,int dep,int dis){
	re;
	if(cmax(len[dep+1],dis))cnt[dep+1]=1;
	else if(len[dep+1]==dis)cnt[dep+1]++;
	scan pd1 update(v,u,dep+1,dis+edge[e].dis);
}
void divide(int u){
	getroot(u,0);vis[u=rt]=1;
	scan pd2 clear(v,0,1);
	len[1]=0,cnt[1]=1;
	scan pd2 
		calc(v,0,1,edge[e].dis),
		update(v,0,1,edge[e].dis);
	scan pd2 total=siz[v],rt=0,divide(v);
}

int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1,u,v,w;i<=m;++i)
		scanf("%d%d%d",&u,&v,&w),
		Edge[u].push_back(mp(v,w)),
		Edge[v].push_back(mp(u,w));
	for(int i=1;i<=n;++i)
		sort(Edge[i].begin(),Edge[i].end());
	dijkstra(),memset(vis,0,sizeof(vis));
	build(1),memset(vis,0,sizeof(vis));
	mxp[0]=total=n;divide(1);
	printf("%d %d\n",ans,num);
	return 0;
}
posted @ 2021-08-24 00:01  _Famiglistimo  阅读(64)  评论(0编辑  收藏  举报