S o l u t i o n : Solution: Solution:
Subtask 1 : 20 p t s \text{Subtask 1}: 20pts Subtask 1:20pts
m = 1 m=1 m=1
好像很可做
O
(
n
)
O(n)
O(n)暴力一下找路径上最长的一条边删去即可
Subtask 2
:
30
p
t
s
\text{Subtask 2}:30pts
Subtask 2:30pts
暴力删边,暴力跑
O
(
n
2
m
)
O(n^2m)
O(n2m),应该能比
Subtask 1
\text{Subtask 1}
Subtask 1多拿两个点#2,#3
就是25分到手
Subtask 3
:
50
p
t
s
\text{Subtask 3}:50pts
Subtask 3:50pts
我们还是暴力删边,但是维护树上路径,我们想到了树剖,所以用树剖维护一下
O
(
n
m
l
o
g
2
n
)
O(nmlog^2n)
O(nmlog2n)
应该可以拿到#1-6,#8-9,#11-12一共10个点
加油,
t
g
tg
tg的
d
2
t
3
d2t3
d2t3我们已经拿到一半了
Subtask 4
:
55
p
t
s
\text{Subtask 4}:55pts
Subtask 4:55pts
我们看之前没过的#7 ,他给出的特殊条件是一条链
一条链就很好搞啦,弄个前缀和维护一下,还是暴力删边,但是每次换删哪条边的时候需要重新跑一边前缀和,复杂福是
O
(
n
⋅
(
n
+
m
)
)
O(n\cdot (n+m))
O(n⋅(n+m)),但是只能过#7,不能过下面的那四个链的点
Subtask 5
:
75
p
t
s
\text{Subtask 5}:75pts
Subtask 5:75pts
我们发现,对于链的情况,其实我们就是要维护区间和, 还有单点修改,所以我们想到了线段树!
我们用线段树维护链上的区间和,这样
O
(
m
l
o
g
n
)
O(mlogn)
O(mlogn)我们就可以把所有链的情况都过了
Subtask 6
:
100
p
t
s
\text{Subtask 6}:100pts
Subtask 6:100pts
上面说了一些
Subtask
\text{Subtask}
Subtask的做法,现在来说一种正解
话说洛谷题解里好像没人写这种做法
题目里面说到
如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?
所以题目让我们求的是所有航线中最长的那条的最小值
所以我们想到了二分,但是二分其实并不好做(可以二分+lca+树上差分,复杂度是
O
(
m
l
o
g
n
l
o
g
∑
t
i
)
O(mlognlog\sum t_i)
O(mlognlog∑ti))在
n
n
n特别大的时候其实复杂度并不是很优秀
我们再回头看一眼题,是一个树上的问题,没有动态删边连边的操作,排除了
l
c
t
lct
lct,那么处理树上问题最常见的方法就是树链剖分了
所以我们把他树剖一下
然后我们考虑最后的答案,我们删的边一定是最长的 k k k条路径上的交集(最长的 k k k条路径一定都经过我们删的哪条边)中的最长的一条,为什么呢?比如说第二长的我们没有选,那么答案一定不会比第二长的路径的长度短(性质1)
然后我们又注意到一条性质,当第 k k k长的边没有覆盖的时候,我们再要求删掉的边在第 k + 1 k+1 k+1长的路径上的时候是没有意义的,因为约束条件变多一定不会比此前的答案更优(性质2)
考虑答案是什么,当我们找出了同时覆盖前
k
k
k大路径的边中最长的那个作为删掉的边,那么答案有可能是
1.最长的路径的长度-删掉的边的长度
2.第
k
+
1
k+1
k+1条路径的最大值
而对于第
2
2
2-
k
k
k长的路径和答案一定是无关的
那么这个时候答案就是1,2取个
max
\max
max
所以我们形成了一个初步的思路,就是首先树剖一下,求出每个路径的长度,为了便于处理,我们按路径长度降序排序, i i i从1到m循环,根据上面的算出答案。当没有一条边满足同时位于前 k k k大路径的时候,根据性质1,2,直接break掉就可以了
那么我们现在需要做的是
1.求路径长度
2.判断是否有一条路径能够满足同时被前
i
i
i长的路径所覆盖
3.求满足条件的中最长的
对于1. ,树剖板子
对于2. ,我们可以另开一个计数器
t
i
m
tim
tim表示这个点被覆盖了多少次,我们每次新加入一条路径的时候,我们把这条路径上的
t
i
m
tim
tim都+1,判断一下这条路径上的最大值,如果等于
i
i
i,说明有,如果小于
i
i
i,那么就是没有
对于3. ,我们可以再开一个变量储存所有被覆盖的次数等于tim的边的边权最大值,那么稍微改一下pushup就可以了
void pushup(int u){
seg[u].val=seg[lc].val+seg[rc].val;
if(seg[lc].tim>seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=seg[lc]._max;
if(seg[rc].tim>seg[lc].tim)seg[u].tim=seg[rc].tim,seg[u]._max=seg[rc]._max;
if(seg[lc].tim==seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=max(seg[lc]._max,seg[rc]._max);
}
但是我们查询2,3应该同时进行,所以我们需要返回一个结构体类型,我是为了节省空间用的pair,要不然我就像往常 一样 直接 用线段树的结构体了qaq
这个做法的复杂度应该是
O
(
n
+
n
+
n
+
n
+
m
l
o
g
2
n
+
m
l
o
g
m
+
m
l
o
g
2
n
)
=
O
(
m
l
o
g
2
n
)
O(n+n+n+n+mlog^2n+mlogm+mlog^2n)=O(mlog^2n)
O(n+n+n+n+mlog2n+mlogm+mlog2n)=O(mlog2n)
(输入边+dfs1+dfs2+build+查询边长+排序+查询)
n,m都是
3
×
1
0
5
3\times10^5
3×105的时候是不用像之前说的二分那样去卡常的
而且这种做法除了代码长点也没有什么细节,唯一的细节就是答案怎么算啦,而且长也主要是正常的板子啦
上代码(总1.89s):
# include <bits/stdc++.h>
using namespace std;
# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)
# define debug puts("QAQ");
typedef long long ll;
const int N=3e5+5;
const int mod=1e9+7;
const double eps=1e-7;
template <typename T> void read(T &x){
x=0;int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
x*=f;
}
int n,m,ans=INT_MAX;
int head[N],cnt;
int faz[N],son[N],dep[N],siz[N],dfn[N],top[N],tot;
int a[N],_a[N];
struct Edge{
int to,next,w;
}e[N<<1];
struct querys{
int x,y,t;
bool operator < (const querys &cmp)const{
return t>cmp.t;
}
}q[N];
void add(int x,int y,int c){
e[++cnt]=(Edge){y,head[x],c},head[x]=cnt;
}
struct segment_tree{
int l,r;
int val,tim,_max;//val表示这一区间的长度,tim表示这条边被标记过几次,_max表示这个区间内被标记过tim次的边的最长权值
int tag;
}seg[N<<2];
# define lc (u<<1)
# define rc (u<<1|1)
void pushup(int u){
seg[u].val=seg[lc].val+seg[rc].val;
if(seg[lc].tim>seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=seg[lc]._max;
if(seg[rc].tim>seg[lc].tim)seg[u].tim=seg[rc].tim,seg[u]._max=seg[rc]._max;
if(seg[lc].tim==seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=max(seg[lc]._max,seg[rc]._max);
}
void pushdown(int u){
seg[lc].tim+=seg[u].tag;
seg[rc].tim+=seg[u].tag;
seg[lc].tag+=seg[u].tag;
seg[rc].tag+=seg[u].tag;
seg[u].tag=0;
}
pair<int,int> merge(pair<int,int> l,pair<int,int> r){
if(l.first>r.first)return l;
if(r.first>l.first)return r;
if(l.first==r.first)return make_pair(l.first,max(l.second,r.second));
}
void build(int u,int l,int r){
seg[u].l=l,seg[u].r=r;
if(l==r){
seg[u].val=seg[u]._max=_a[l];
seg[u].tim=0;
return;
}
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(u);
}
void update(int u,int l,int r,int k){
if(seg[u].l>=l&&seg[u].r<=r){
seg[u].tim++;
seg[u].tag++;
return;
}
if(seg[u].tag)pushdown(u);
int mid=seg[u].l+seg[u].r>>1;
if(l<=mid)update(lc,l,r,k);
if(r>mid)update(rc,l,r,k);
pushup(u);
}
int Getlen(int u,int l,int r){
if(seg[u].l>=l&&seg[u].r<=r)return seg[u].val;
if(seg[u].tag)pushdown(u);
int mid=seg[u].l+seg[u].r>>1;
int res=0;
if(l<=mid)res+=Getlen(lc,l,r);
if(r>mid)res+=Getlen(rc,l,r);
return res;
}
pair<int,int> query(int u,int l,int r){
if(seg[u].l>=l&&seg[u].r<=r)return make_pair(seg[u].tim,seg[u]._max);
if(seg[u].tag)pushdown(u);
int mid=seg[u].l+seg[u].r>>1;
if(r<=mid)return query(lc,l,r);
if(l>mid)return query(rc,l,r);
return merge(query(lc,l,r),query(rc,l,r));
}
void RouteModify(int x,int y,int k){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,dfn[top[x]],dfn[x],k);
x=faz[top[x]];
}
if(x!=y){
if(dep[x]>dep[y])swap(x,y);
update(1,dfn[x]+1,dfn[y],k);
}
}
int RouteQuerylen(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res+=Getlen(1,dfn[top[x]],dfn[x]);
x=faz[top[x]];
}
if(x!=y){
if(dep[x]>dep[y])swap(x,y);
res+=Getlen(1,dfn[x]+1,dfn[y]);
}
return res;
}
pair<int,int> RouteQuery(int x,int y){
pair<int,int> res;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res=merge(res,query(1,dfn[top[x]],dfn[x]));
x=faz[top[x]];
}
if(x!=y){
if(dep[x]>dep[y])swap(x,y);
res=merge(res,query(1,dfn[x]+1,dfn[y]));
}
return res;
}
void dfs1(int u,int fa){
faz[u]=fa;
siz[u]=1;
dep[u]=dep[fa]+1;
RepG(i,u){
int v=e[i].to;
if(v==fa)continue;
a[v]=e[i].w;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
void dfs2(int u,int _top){
top[u]=_top;
dfn[u]=++tot;
_a[tot]=a[u];
if(!son[u])return;
dfs2(son[u],_top);
RepG(i,u){
int v=e[i].to;
if(v==faz[u]||v==son[u])continue;
dfs2(v,v);
}
}
int main()
{
memset(head,-1,sizeof(head));
read(n),read(m);
Rep(i,1,n-1){
int x,y,c;
read(x),read(y),read(c);
add(x,y,c),add(y,x,c);
}
dfs1(1,0),dfs2(1,1);
build(1,1,n);
Rep(i,1,m){
read(q[i].x),read(q[i].y);
q[i].t=RouteQuerylen(q[i].x,q[i].y);
}
sort(q+1,q+m+1);
ans=q[1].t;
Rep(i,1,m){
RouteModify(q[i].x,q[i].y,1);
pair<int,int> res=RouteQuery(q[i].x,q[i].y);
if(res.first<i)
break;
ans=min(ans,max(q[1].t-res.second,q[i+1].t));
}
printf("%d\n",ans);
return 0;
}
第一道自己写的 d a y 2 T 3 day2T3 day2T3耶,开森