[luogu 1399][NOI2013]快餐店
link:https://www.luogu.org/problemnew/show/P1399
题意
给出一个基环树,每条边有一个边权,在图上求出一个点(可以在边上)使得它到图上每个节点的最大距离最小,输出那个距离.
题解
可以直接看代码
一个和很直观的思路,对于基环树,只需要断开换上的一条边然后求树的直径就可以了,可以证明断开一条边后对答案没有影响,因为距离肯定不会成一个环。
但是这样也是
o
(
n
2
)
o(n^2)
o(n2)。
可以先对换上的每个外向树做一次求直径(最后的答案肯定大于等于它的一半的),求出
f
[
i
]
f[i]
f[i]表示以第
i
i
i个点为起点,做大能在子树中延伸的长度。
然后对于换上
i
,
j
i,j
i,j两点,他们的距离就是
f
[
i
]
+
f
[
j
]
+
d
i
s
[
i
]
[
j
]
f[i]+f[j]+dis[i][j]
f[i]+f[j]+dis[i][j],
d
i
s
[
i
]
[
j
]
dis[i][j]
dis[i][j]表示的是在环上
i
,
j
i,j
i,j的直接距离。
拆换成链后就变成
f
[
i
]
+
f
[
j
]
+
s
u
m
[
j
]
−
s
u
m
[
i
]
f[i]+f[j]+sum[j] - sum[i]
f[i]+f[j]+sum[j]−sum[i],
s
u
m
[
i
]
sum[i]
sum[i]表示拆环成链后边权的前缀和,然后
f
[
i
]
−
s
u
m
[
i
]
f[i]-sum[i]
f[i]−sum[i]和
f
[
j
]
−
s
u
m
[
j
]
f[j]-sum[j]
f[j]−sum[j]直接开两个堆维护一下最大值就可以了,注意要考虑
i
=
=
j
i == j
i==j的情况。
是不是很简单(毒瘤)
#include<bits/stdc++.h>
#define int long long
#define N 1000005
using namespace std;
struct edge{
int v, nxt, c;
}e[N << 1];
int p[N], eid;
void init(){
memset(p, -1, sizeof p);
eid = 0;
}
void insert(int u, int v, int c){
e[eid].v = v;
e[eid].c = c;
e[eid].nxt = p[u];
p[u] = eid ++;
}
int dfn[N], n, fa[N], f[N], F, ring[N], cnt, ha[N], ans, tot, sum[N], val[N];
void findring(int u){
if(dfn[u]) F = dfn[u];
if(F) return;
dfn[u] = ++ tot;
for(int i = p[u]; i + 1; i = e[i].nxt){
int v = e[i].v;
if(v == fa[u]) continue;
fa[v] = u;
findring(v);
}
if(F) ring[++ cnt] = u, ha[u] = 1;
if(F == dfn[u]) F = 0;
}
void dfs(int u){ //dp
for(int i = p[u]; i + 1; i = e[i].nxt){
int v = e[i].v;
if(v == fa[u] || ha[v]) continue;
fa[v] = u;
dfs(v);
ans = max(ans, f[u] + f[v] + e[i].c);//求树的直径
f[u] = max(f[u], f[v] + e[i].c);//第u为起点,做大能在子树中延伸的长度
}
}
struct cmp1{int operator()(int x, int y){
return f[ring[x]] - sum[x] < f[ring[y]] - sum[y];
}};
struct cmp2{int operator()(int x, int y){
return f[ring[x]] + sum[x] < f[ring[y]] + sum[y];
}};
priority_queue<int, vector<int>, cmp1> q1;//维护f[i]-sum[i]
priority_queue<int, vector<int>, cmp2> q2;//维护f[i]+sum[i]
signed main(){
init();
scanf("%lld", &n);
for(int i = 1; i <= n; i ++){
int u, v, c;
scanf("%lld%lld%lld", &u, &v, &c);
insert(u, v, c);
insert(v, u, c);
}
findring(1);
memset(fa, 0, sizeof fa);
for(int i = 1; i <= cnt; i ++) dfs(ring[i]);//对于环上的每个点向外延伸的树做一次dp
ring[0] = ring[cnt];
for(int i = 1; i <= cnt; i ++){//把环的边权表示成序列的形式
int u = ring[i];
for(int j = p[u]; j + 1; j = e[j].nxt){
int v = e[j].v;
if(v == ring[i - 1]) val[i] = e[j].c;
}
}
for(int i = 1; i <= cnt;i ++) val[i + cnt] = val[i], ring[i + cnt] = ring[i];//拆环成链
for(int i = 1; i <= cnt * 2; i ++) sum[i] = sum[i - 1] + val[i];//做环上边权的前缀和
int anss = 1e18;
for(int i = 1; i <= cnt; i ++) q1.push(i), q2.push(i);
for(int i = 1; i <= cnt; i ++){
while(q1.top() < i && q1.size() > 1) q1.pop();
while(q2.top() < i && q2.size() > 1) q2.pop();
int x = q1.top(), y = q2.top();
if(x == y){//要靠考虑i == j的情况,每个再取出一个最大值,然后交叉组合看那个大
q1.pop(), q2.pop();
while(q1.top() < i && q1.size() > 1) q1.pop();
while(q2.top() < i && q2.size() > 1) q2.pop();
int xx = q1.top(), yy = q2.top();
anss = min(anss, max(f[ring[x]] - sum[x] + f[ring[yy]] + sum[yy], f[ring[xx]] - sum[xx] + f[ring[y]] + sum[y]));//硬套刚刚的公式
q1.push(x), q2.push(y);
}else anss = min(anss, f[ring[x]] - sum[x] + f[ring[y]] + sum[y]);//不然就直接搞
q1.push(cnt + i), q2.push(cnt + i);
}
ans = max(anss, ans);//和之前树的直径求个最大值
printf("%lld.%lld", ans/2, (ans&1)? 5:0);//输出
return 0;
}