[FJOI2014]最短路径树问题

LG传送门

B站传送门

长链剖分练手好题。

如果你还不会长链剖分的基本操作,可以看看我的总结

这题本来出的很没水平,就是dijkstra(反正我是不用SPFA)的板子强行套个点分治的板子,两者之间没有任何关联。但我偏要写长链剖分。

首先给你的是一张图,要转成一棵题目所要求的树,先把出边按到达点的编号排个序再跑个单源最短路,然后一遍dfs把树建出来就好了。这不是本文的重点(如果用点分治也要这样写),如果没搞懂看看代码就好了。

建出一棵树之后,把某个点连向其父亲的边的边权定为这个点的点权(我写长链剖分习惯处理点权),然后就可以写DP了,按照一般套路先写\(n^2\)DP。容易想到设\(f[x][j]\)表示\(x\)子树中从\(x\)往下延伸的深度为\(j\)的链上的最大点权和,设\(c[x][j]\)表示满足上述条件的链的条数,合并轻儿子时用轻儿子和重儿子的信息更新答案,再用轻儿子的信息更新重儿子的信息,最后用重链上的信息更新一下答案。

但是我们发现在一个点\(i\)继承其重儿子\(q[x]\)的信息时,需要对\(f[x][]\)中的每一个数加上\(q[x]\)的点权,而直接这样做是\(O(n)\)的,那我们长链剖分的复杂度就会掉到\(O(n^2)\),和暴力没区别。于是考虑优化。

开一个标记数组\(w[x]\)(可以直接用记录点权的数组,我就是这样写的),表示在处理\(f[x][]\)数组时,里面每一个数的实际值都是记录在数组里的值加上\(w[i]\)的结果。转移标记就是\(w[x]+=w[q[x]]\)。在合并轻儿子时,用\(f[x][K-j]+f[y][j]+w[q[x]]+w[y]\)来更新答案(\(q[x]\)是重儿子,\(K\)的值是题目所给的值\(-2\)(至于为什么,可以自己手玩),\(y\)是轻儿子),这样就统计了跨过点\(x\)的答案,然后用\(f[y][j]+w[y]-w[q[x]]\)来更新\(f[x][j+1]\)的信息,再用\(f[x][K+1]+w[q[x]]\)来统计一下重链上的答案就好了。但是要注意每个\(f[x][0]=-w[q[x]]\),这样是为了方便转移。从我的描述中就可以看到,我们打的标记并不会对复杂度产生影响,仍是优秀的\(O(n)\)。

实现起来就是细节有点多,打的时候注意。(\(f[x][j]\)和\(c[x][j]\)是用结构体记的)

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define R register
#define I inline
#define P push_back
#define M make_pair
#define O first
#define T second
using namespace std;
const int S=30003;
char buf[S],*p1,*p2;
I char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,S,stdin),p1==p2)?EOF:*p1++;}
I int rd(){
R int f=0; R char c=gc();
while(c<48||c>57) c=gc();
while(c>47&&c<58) f=f*10+(c^48),c=gc();
return f;
}
struct N{int f,c;N(){f=0,c=1;}}u[S],*f[S],*e=u+1;
int h[S],s[S],g[S],b[S],k[S],d[S],t[S],p[S],q[S],w[S],c,o,l,K;
vector<pair<int,int> > v[S]; priority_queue<pair<int,int> > Q;
I int max(int x,int y){return x>y?x:y;}
I void add(int x,int y){s[++c]=h[x],h[x]=c,g[c]=y,b[y]=1,p[y]=x;}
void dfs0(int x){
for(R int i=0,y,z,m=v[x].size();i<m;++i)
if(!b[y=v[x][i].O]&&k[x]+(z=v[x][i].T)==k[y]) add(x,y),w[y]=z,dfs0(y);
}
void dfs1(int x){t[x]=d[x]=d[p[x]]+1;
for(R int i=h[x],y;i;i=s[i])
if((y=g[i])^p[x]){dfs1(y);
if(t[y]>t[x]) t[x]=t[y],q[x]=y;
}
}
void dfs2(int x){f[x][0].c=1;
R int i,j,y,z,u,v,m; if(!q[x]) return ;
f[q[x]]=f[x]+1,dfs2(q[x]),w[x]+=(v=w[q[x]]),f[x][0].f-=v;
for(i=h[x];i;i=s[i])
if((y=g[i])^p[x]&&y^q[x]){f[y]=e,m=t[y]-d[y]+1,e+=m,dfs2(y),u=w[y];
for(j=max(K-t[x]+d[x],0);j<m&&K-j;++j)
if((z=f[y][j].f+f[x][K-j].f+u+v)>o) o=z,l=f[y][j].c*f[x][K-j].c;
else if(o==z) l+=f[x][K-j].c*f[y][j].c;
for(j=0;j<m;++j)
if((z=f[y][j].f+u-v)>f[x][j+1].f)
f[x][j+1].f=z,f[x][j+1].c=f[y][j].c;
else if(z==f[x][j+1].f) f[x][j+1].c+=f[y][j].c;
}
if(K+1>t[x]-d[x]+1) return ;
if((z=f[x][K+1].f+v)>o) o=z,l=f[x][K+1].c; else if(o==z) l+=f[x][K+1].c;
}
int main(){
R int n=rd(),m=rd(),i,x,y,z;
for(K=rd()-2,i=1;i<=m;++i) x=rd(),y=rd(),z=rd(),v[x].P(M(y,z)),v[y].P(M(x,z));
for(i=1;i<=n;++i) sort(v[i].begin(),v[i].end());
memset(k,0x3f,sizeof k),k[1]=0,Q.push(M(0,1));
while(!Q.empty()){x=Q.top().T,Q.pop();
if(b[x]) continue; b[x]=1;
for(i=0,m=v[x].size();i<m;++i)
if(k[y=v[x][i].O]>k[x]+(z=v[x][i].T))
k[y]=k[x]+z,Q.push(M(-k[y],y));
}
memset(b,0,sizeof b),dfs0(1),dfs1(1),f[1]=e,e+=t[1],dfs2(1);
printf("%d %d",o,l);
return 0;
}

本来以为可以复杂度踩点分治,仔细一看才想起dij的复杂度是\(O(nlogn)\)的,总的复杂度还是\(nlogn\),只能夸夸自己常数小了。

05-08 15:11