BZOJ3242/UOJ126 [Noi2013]快餐店
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
Description
小T打算在城市C开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小T希望快餐店的地址选在离最远的顾客距离最近的地方。 快餐店的顾客分布在城市C的N 个建筑中,这N 个建筑通过恰好N 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成的路径。小T的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。 现给定城市C的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。
Input
第一行包含一个整数N,表示城市C中的建筑和道路数目。
接下来N行,每行3个整数,Ai,Bi,Li(1≤i≤N;Li>0),表示一条道路连接了建筑Ai与Bi,其长度为Li 。
Output
仅包含一个实数,四舍五入保留恰好一位小数,表示最佳快餐店选址距离最远用户的距离。
注意:你的结果必须恰好有一位小数,小数位数不正确不得分。
Sample Input
1 4 2
1 3 2
2 4 1
Sample Output
HINT
数据范围
对于 10%的数据,N<=80,Li=1;
对于 30%的数据,N<=600,Li<=100;
对于 60% 的数据,N<=2000,Li<=10^9;
对于 100% 的数据,N<=10^5,Li<=10^9
正解:树形$DP+dfs$
解题报告:
首先我们要明确,直接求图的直径是错误的。
反例可以想想,大概就是在环上等距的三个点,然后其中一个点往外拓展了一棵树,由于我求出来之后按理要放在直径的中点上,但这种情况显然不满足。
下面我们需要考虑如何求一个点,使得它到图中的所有点的最大距离最小。
对于这种带了环的题目(又是基环外向树),我们一般是断掉环上的某一条边,再作考虑。
考虑断掉环上一条边,然后求树的直径的正确性,显然一条合法的路径肯定不能经过一整个环,那么就有一些部分是肯定不会经过的,我就可以枚举这些肯定不会经过的一条边,断掉,对于树与树之间肯定无影响,对环上的点,由于有两条路径,删掉不会导致“错过”最优答案。
断掉一条边之后变成一棵树,对于树而言求直径肯定就是对的了。
如果能快速地查询断掉这条边之后对得到的树的直径的查询就能得到答案。
我们先$dfs$一遍得到环上每个点拓展出去的树的直径,最终答案不会小于等于最大直径$/2$。
我们先人工地断开$1$到$cnt$(假设环长为$cnt$)的边,从前往后求一遍$u$,$v$:$u$表示的是$max(前缀中最大的一条链+当前节点的树的最大深度)$,$v$表示的是$max(前缀中两棵树的最大深度+这两个根节点直接的距离)$,后缀也做一遍,记为$u2,v2$。
那么我最后枚举断掉哪条边时,就可以快速合并了!
我们枚举断掉$i$到$i+1$这条边时,答案应该是$max(max(v[i],v2[i+1]),u[i]+u2[i+1]+1$到$cnt$的边权)。
公式的含义就是,前缀的一段的最大直径,后缀的一段的最大直径,加上,前缀和后缀的$u$经过$1$到$cnt$这条边之后组合起来的直径,三个取一个$max$即为答案。
我们显然需要使得这个最短。
最后我们需要和最大的树的直径取一个$max$,原因上面已经说了...
ps:话说这道题似乎线段树也可以做,不过复杂不少,而且细节很多,还多了一个$log$...
//It is made by ljh2000 #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <ctime> #include <vector> #include <queue> #include <map> #include <set> #include <string> #include <complex> using namespace std; typedef long long LL; const int MAXN = 100011; const int MAXM = 200011; int n,ecnt,first[MAXN],to[MAXM],next[MAXM],w[MAXM]; int b[MAXN],scnt,father[MAXN],val[MAXN],fa_w[MAXN];//val[i]表示环上第i个点到第i+1个点的距离 bool iscircle[MAXN],vis[MAXN]; LL f[MAXN],pre[MAXN],best[MAXN],pre2[MAXN],best2[MAXN],ans,chain; inline int getint(){ int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w; } inline void dfs_find(int x,int fa){ vis[x]=1; for(int i=first[x];i;i=next[i]) { int v=to[i]; if(v==fa) continue; if(vis[v]) { if(iscircle[v]) continue; int u=x; while(u!=v) { iscircle[u]=true; b[++scnt]=u; val[scnt]=fa_w[u]; u=father[u]; } iscircle[u]=true; b[++scnt]=v; val[scnt]=w[i]; continue; } father[v]=x; fa_w[v]=w[i]; vis[v]=true; dfs_find(v,x); } } inline void dfs(int x,int fa){ for(int i=first[x];i;i=next[i]) { int v=to[i]; if(v==fa || iscircle[v]) continue; dfs(v,x); chain=max(chain,f[x]+f[v]+w[i]); f[x]=max(f[x],f[v]+w[i]); } } inline void work(){ n=getint(); int x,y,z; for(int i=1;i<=n;i++) { x=getint(); y=getint(); z=getint(); next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z; next[++ecnt]=first[y]; first[y]=ecnt; to[ecnt]=x; w[ecnt]=z; } dfs_find(1,0); LL tot=0,da=0; int now; for(int i=1;i<=scnt;i++) dfs(b[i],0); for(int i=1;i<=scnt;i++) { tot+=val[i-1];//环上权值前缀和 now=b[i]; pre[i]=max(pre[i-1],tot+f[now]); best[i]=max(best[i-1],tot+da+f[now]); da=max(da,f[now]-tot);//方便后面组合得到完整答案 } tot=0; LL duan=val[scnt]; val[scnt]=da=0; for(int i=scnt;i>=1;i--) { tot+=val[i]; now=b[i]; pre2[i]=max(pre2[i+1],tot+f[now]); best2[i]=max(best2[i+1],tot+da+f[now]); da=max(da,f[now]-tot); } ans=best[scnt]; LL choose; for(int i=1;i<scnt;i++) { choose=max(best[i],best2[i+1]); choose=max(choose,pre[i]+pre2[i+1]+duan); if(choose<ans) ans=choose; } ans=max(ans,chain); if(ans&1) printf("%lld.5",ans>>1); else printf("%lld.0",ans>>1); } int main() { work(); return 0; }