NOI2013 快餐店
Description
小T打算在城市C开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小T希望快餐店的地址选在离最远的顾客距离最近的地方。
快餐店的顾客分布在城市C的N 个建筑中,这N 个建筑通过恰好N 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成 的路径。小T的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。
现给定城市C的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。
Input
第一行包括一个整数n表示城市C中建筑物与道路的数目
接下来N行,每行包括三个整数Ai,Bi,Li(1<=i<=n,Li>0)表示一条道路连接了Ai,Bi,其长度为Li
Output
输出仅为一个实数四舍五入保留恰好一位小数表示最佳快餐店选址离最远用户的距离
Sample Input
【样例输入1】
4
1 2 1
1 4 2
1 3 2
2 4 1
【样例输入2】
5
1 5 100
2 1 77
3 2 80
4 1 64
5 3 41
Sample Output
【样例输出1】
2.0
【样例输出2】
109.0
Hint
对于 10%的数据,N<=80,Li=1;
对于 30%的数据,N<=600,Li<=100;
对于 60% 的数据,N<=2000,Li<=10^9;
对于 100% 的数据,N<=10^5,Li<=10^9
Solution:
首先 m=n ,这是一个基环外向树的结构。
60分是非常好写的,因为这个图里有且仅有一个环,因此这个环上肯定有一条边是不走的。因此直接枚举这条边,剩下的就是一棵树了,显然应该建在树的重心上,答案就是 D/2 ,其中 D 表示剩下的树的直径。在这些答案中取个最小值,就可以了。因为抽环比较麻烦,所以直接全部枚举,再检查一下剩下的树中是否还有环以及是否连通就可以了。
#include<ctime>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#define ll long long
#define lson (p<<1)
#define rson (p<<1|1)
#define lowbit(x) ((x)&(-x))
#define siz(x) ((int)(x).size())
using namespace std;
inline void Rd(int &res){
char c;res=0;
while(c=getchar(),c<'0');
do{
res=(res<<1)+(res<<3)+(c^48);
}while(c=getchar(),c>='0');
}
const int M=(int)1e5+5;
struct Edge{int to,w,nxt;}Edge[M<<1];
int Head[M],tot,degree[M];
int n;
inline void Addedge(int a,int b,int c){
Edge[++tot].to=b;Edge[tot].w=c;Edge[tot].nxt=Head[a];Head[a]=tot;
Edge[++tot].to=a;Edge[tot].w=c;Edge[tot].nxt=Head[b];Head[b]=tot;
}
struct Q{int a,b;}Q[M];
struct P60{
int Igra,Igrb,id;
ll mx;
bool vis[M],flag;
void dfs(int x,int f,ll d){
if(d>mx)mx=d,id=x;
if(vis[x]){//环
flag=false;
return;
}
vis[x]=true;
for(int i=Head[x];~i;i=Edge[i].nxt){
int to=Edge[i].to;
if(to==f)continue;
if(to==Igra&&x==Igrb)continue;
if(to==Igrb&&x==Igra)continue;
dfs(to,x,d+Edge[i].w);
}
}
void solve(){
double ans=-1;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
Igra=Q[i].a;Igrb=Q[i].b;
flag=true;
mx=-1;dfs(Q[i].a,0,0);
for(int j=1;j<=n;j++)
if(!vis[j]){flag=false;break;}//不连通
if(!flag)continue;
memset(vis,0,sizeof(vis));
mx=-1;dfs(id,0,0);
if(ans==-1||mx/2.0<ans)ans=mx/2.0;
}
printf("%.1f\n",ans);
}
}P60;
int main(){
// freopen("foodshop.in","r",stdin);
// freopen("foodshop.out","w",stdout);
memset(Head,-1,sizeof(Head));
Rd(n);
for(int i=1;i<=n;i++){
int a,b,c;
Rd(a),Rd(b),Rd(c);
Q[i].a=a;Q[i].b=b;
Addedge(a,b,c);
}
if(n<=2000)P60.solve();
}
然后考虑正解,将环抽出来并复制一份成为两倍长度的链。仍然考虑断边,最后的答案有可能来自外向树树中的直径或者是经过环上边的最长路径。前者直接
其中 sz 是环的长度,2倍 sz 的原因是为了环变成链。
如果不考虑
L<R
这个条件,这就是一个标准的
RMQ
问题了。而实际上
L<R
根本不用考虑。因为
sum
单调递增,若
L>R
则交换
L
与
当我们断开一条环上的边 i→i+1 时,其实就对应着我们的链上的一段区间 [i+1,i+sz] 。
于是我们建立两颗线段树,分别维护 sum+dp 与 sum−dp ,求出区间 [1,sz],[2,sz+1],[3,sz+2]…[sz,sz∗2−1] 中的 res=Max(sum+dp)+Max(sum−dp) ,在这些区间中取一个 res 最小的。最后与外向树上的直径取个最大值除2,就是答案了。
但是这里还有一个问题,我们Query出来的两个点的下标有可能是相同的,而这时实际上不合法的。于是在线段树节点上同时保存最大值与次大值就可以了。
于是这题就可以用线段树解决了,复杂度 O(nlogn) 。
#include<ctime>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#define ll long long
#define lson (p<<1)
#define rson (p<<1|1)
#define lowbit(x) ((x)&(-x))
#define siz(x) ((int)(x).size())
using namespace std;
inline void Rd(int &res){
char c;res=0;
while(c=getchar(),c<'0');
do{
res=(res<<1)+(res<<3)+(c^48);
}while(c=getchar(),c>='0');
}
const int M=(int)1e5+5;
struct Edge{int to,w,nxt;}Edge[M<<1];
struct Node{int x,d;}Q[M<<1],fa[M];
int Head[M],tot,n,tot_time,id[M],sz,mx_id;
ll d,dep[M<<1],Max,Min=-1,mx,sum[M<<1];
bool InCir[M];
inline void Addedge(int a,int b,int c){
Edge[++tot].to=b;Edge[tot].w=c;Edge[tot].nxt=Head[a];Head[a]=tot;
Edge[++tot].to=a;Edge[tot].w=c;Edge[tot].nxt=Head[b];Head[b]=tot;
}
void dfs_cir(int x){//抽环
id[x]=++tot_time;//时间戳
for(int i=Head[x];~i;i=Edge[i].nxt){
int to=Edge[i].to;
if(!id[to]){
fa[to].x=x;
fa[to].d=Edge[i].w;
dfs_cir(to);
}else if(id[to]>id[x]){
int t=to;
while(t!=x){
InCir[t]=true;
Q[++sz].x=t;
Q[sz].d=fa[t].d;
t=fa[t].x;
}
InCir[x]=true;
Q[++sz].x=x;
Q[sz].d=Edge[i].w;
return;
}
}
}
void dfs_tree(int x,int f,ll d){
if(d>mx)mx=d,mx_id=x;
for(int i=Head[x];~i;i=Edge[i].nxt){
int to=Edge[i].to;
if(to==f||InCir[to])continue;
dfs_tree(to,x,d+Edge[i].w);
}
}
struct tree{
ll mx1,mx2;
int id1,id2;
tree(){id1=id2=-1;}
};
struct SegTree{
tree Tree[M<<3];
int f;
tree Up(tree A,tree B){
tree res;
if(B.id1==-1||A.mx1>B.mx1){
res.mx1=A.mx1;
res.id1=A.id1;
if(B.id1==-1||(~A.id2&&A.mx2>B.mx1)){
res.mx2=A.mx2;
res.id2=A.id2;
}else{
res.mx2=B.mx1;
res.id2=B.id1;
}
}else{
res.mx1=B.mx1;
res.id1=B.id1;
if(A.id1==-1||(~B.id2&&B.mx2>A.mx1)){
res.mx2=B.mx2;
res.id2=B.id2;
}else{
res.mx2=A.mx1;
res.id2=A.id1;
}
}
return res;
}
void Build(int L,int R,int p){
if(L==R){
Tree[p].mx1=dep[L]+f*sum[L];
Tree[p].id1=L;
return;
}
int mid=(L+R)>>1;
Build(L,mid,lson);
Build(mid+1,R,rson);
Tree[p]=Up(Tree[lson],Tree[rson]);
}
tree Query(int L,int R,int l,int r,int p){
if(L==l&&r==R){
return Tree[p];
}
int mid=(L+R)>>1;
if(r<=mid)return Query(L,mid,l,r,lson);
else if(l>mid)return Query(mid+1,R,l,r,rson);
else{
tree A=Query(L,mid,l,mid,lson),B=Query(mid+1,R,mid+1,r,rson);
return Up(A,B);
}
}
}T[2];
int main(){
// freopen("foodshop.in","r",stdin);
// freopen("foodshop.out","w",stdout);
memset(Head,-1,sizeof(Head));
Rd(n);
for(int i=1;i<=n;i++){
int a,b,c;
Rd(a),Rd(b),Rd(c);
Addedge(a,b,c);
}
dfs_cir(1);
for(int i=1;i<=sz;i++){
InCir[Q[i].x]=false;
mx=-1;dfs_tree(Q[i].x,0,0);
dep[i]=mx;
dfs_tree(mx_id,0,0);//找直径
InCir[Q[i].x]=true;
if(mx>Max)Max=mx;
}
for(int i=1;i<=sz;i++)Q[i+sz]=Q[i],dep[i+sz]=dep[i];
for(int i=1;i<=sz<<1;i++)sum[i]=sum[i-1]+Q[i-1].d;
T[0].f=1;T[1].f=-1;
T[0].Build(1,sz<<1,1);
T[1].Build(1,sz<<1,1);
for(int i=1;i<=sz;i++){
tree A=T[0].Query(1,sz<<1,i,i+sz-1,1);
tree B=T[1].Query(1,sz<<1,i,i+sz-1,1);
ll rs;
if(A.id1==B.id1)rs=max(A.mx1+B.mx2,A.mx2+B.mx1);//同一点
else rs=A.mx1+B.mx1;
if(Min==-1||rs<Min)Min=rs;
}
printf("%.1f\n",max(Max,Min)/2.0);
return 0;
}