bzoj 3242: [Noi2013]快餐店
Description
小T打算在城市C开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小T希望快餐店的地址选在离最远的顾客距离最近的地方。 快餐店的顾客分布在城市C的N 个建筑中,这N 个建筑通过恰好N 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成的路径。小T的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。 现给定城市C的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。
Solution
还比较可做,容易发现树的情况就是直径除以二
基环外向树也类似,其实就是所有可能的最点对的最短路取个 \(max\),也就是 \(max(dis(x,y))\),\(dis(x,y)\) 表示 \(x\)到\(y\) 的最短路,于是就可以做 \(O(n^2)\) 了
实际上如果经过了环了话,那么路径就有两条
我们发现其实这些路径很多都可以归为一类,其实就是断掉环上的某个点后的树中的直径,小于直径的点对就没有必要再考虑了,因为肯定不会算入 \(max(dis(x,y))\) 中
所以问题转化为:我们需要断掉环上的任意一条边,并求出形成的树的直径,并将直径取 \(min\)
这个问题就比较简单了
对于不经过环的可以直接树形DP求出来.
对于经过环的,我们可以任选两个外向树的最长链拼起来,答案就是所有拼法中的最大值
我们给环编个号 \(1,2,3,...cnt\) ,那么路径就只有两种情况,一种是经过点 \(cnt\) 的,一种是不经过的
分开讨论即可,考虑不经过的,设 \(pri[i]\) 表示前 \(i\) 棵外向树构成的最长路,\(f[i]\) 表示第 \(i\) 棵外向树的最长链的长度
\(pri[i]=max(pri[i-1],f[i]+f[v]+dis(u,v))\)
我们可以把 \(dis(u,v)\) 拆成 \(dis[u]-dis[v]\) ,那么就可以维护一个 \(f[v]-dis[v]\) 的最大值优化转移了
复杂度 \(O(n)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int head[N],nxt[N<<1],to[N<<1],num=1,n,dis[N<<1],pre[N];bool vis[N];
inline void link(int x,int y,int z){
nxt[++num]=head[x];to[num]=y;head[x]=num;dis[num]=z;}
bool flag=0;int c[N],cnt=0;ll d[N],f[N],ans=0;
inline void circle(int x,int y){
while(x!=y){
c[++cnt]=x;
d[cnt+1]=d[cnt]+dis[pre[x]];
x=to[pre[x]^1];
}
c[++cnt]=y;
}
inline void dfs(int x,int last){
vis[x]=1;
for(int i=head[x];i;i=nxt[i]){
int u=to[i];if(u==last)continue;
if(!vis[u])pre[u]=i,dfs(u,x);
else pre[u]=i,circle(x,u),flag=1;
if(flag)return ;
}
vis[x]=0;
}
inline void DFS(int x,int last){
for(int i=head[x];i;i=nxt[i]){
int u=to[i];
if(u==last || vis[u])continue;
DFS(u,x);ans=max(ans,f[x]+f[u]+dis[i]);
f[x]=max(f[x],f[u]+dis[i]);
}
}
ll pri[N],suf[N],p[N],q[N];
int main(){
freopen("pp.in","r",stdin);
freopen("pp.out","w",stdout);
int x,y,z;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&x,&y,&z);
link(x,y,z);link(y,x,z);
}
dfs(1,1);
for(int i=1;i<=n;i++)vis[i]=0;
for(int i=1;i<=cnt;i++)vis[c[i]]=1;
for(int i=1;i<=cnt;i++)DFS(c[i],c[i]);
ll mx=f[c[1]],my=f[c[1]],ret=1e15,res=0,tot=d[cnt]+dis[pre[c[cnt]]];
pri[1]=p[1]=f[c[1]];
for(int i=2;i<=cnt;i++){
pri[i]=max(pri[i-1],mx+f[c[i]]+d[i]);
p[i]=max(f[c[i]],-d[i]+my);
mx=max(mx,f[c[i]]-d[i]);my=max(my,f[c[i]]+d[i]);
}
suf[cnt]=q[cnt]=f[c[cnt]];mx=d[cnt]+f[c[cnt]];my=f[c[cnt]]-d[cnt];
for(int i=cnt-1;i>=1;i--){
suf[i]=max(suf[i+1],mx+f[c[i]]-d[i]);
q[i]=max(f[c[i]],d[i]+my);
mx=max(mx,f[c[i]]+d[i]);my=max(my,f[c[i]]-d[i]);
}
ret=max(max(pri[cnt],suf[1]),tot-dis[pre[c[cnt]]]);
for(int i=1;i<cnt;i++){
res=max(pri[i],suf[i+1]);
res=max(res,tot+p[i]+q[i+1]-dis[pre[c[i]]]);
ret=min(res,ret);
}
ans=max(ans,ret);
printf("%.1lf\n",ans/2.0);
return 0;
}