P9340 [JOISC 2023 Day3] Tourism 题解
记一下。称
法一:
最好想的。
有个显然的结论,将所有关键点按 DFS 序排序,走过的边的数量为排序后相邻的点之间的距离。记走过的边的数量为
法二:
可以用 bitset 按
法三:
还有一种计算点集大小的方法,用数据结构维护一下,直接标记所有关键点走到某一个节点所经过的边,然后查询被标记的边的数量,这样配合回滚莫队也可以做到
想一下为什么会这么麻烦。发现是因为必须要钦定某一个特定的点与其它点的路径标记。考虑能否将这个点固定下来。钦定这个点为
考虑离线后按
暴力的维护是简单的,直接上分块即可。
但这样的时间复杂度依然很高。想想区间赋值,可以直接用 ODT。这里 ODT 的复杂度显然是可以保证的,但是每次查询暴力扫一遍
有更优的实现方式:参考 Leasier 的思路,发现是对每条重链前缀赋值,可以使用 vector 替代 ODT。
时间复杂度:
空间复杂度:
代码:
const int N=1e5+10;
int n,m,Q;
int a[N],st[18][N],log_2[N],ans[N];
struct BIT{
int v[N];
void add(int x,int y){assert(x);for(;x<=m;x+=x&-x)v[x]+=y;}
int ask(int x){
int res=0;
for(;x;x-=x&-x)res+=v[x];
return res;
}
} T;
int head[N],cnt_e=1;
struct edge{int v,nxt;} e[N<<1];
void adde(int u,int v){
e[++cnt_e]=(edge){v,head[u]};
head[u]=cnt_e;
}
int dfc,dfn[N],rdfn[N],sz[N],hv[N],d[N],fa[N],top[N];
void dfs1(int u,int fath){
sz[u]=1,d[u]=d[fath]+1,fa[u]=fath;
for(int i=head[u],v;i;i=e[i].nxt)if((v=e[i].v)!=fath){
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[hv[u]])hv[u]=v;
}
}
void dfs2(int u,int tp){
rdfn[dfn[u]=++dfc]=u,top[u]=tp;
if(!hv[u])return;
dfs2(hv[u],tp);
for(int i=head[u],v;i;i=e[i].nxt)if((v=e[i].v)!=hv[u]&&v!=fa[u])dfs2(v,v);
}
int glca(int u,int v){
while(top[u]!=top[v]){
if(d[top[u]]<d[top[v]])swap(u,v);
u=fa[top[u]];
}
if(d[u]<d[v])swap(u,v);
return v;
}
int qlca(int l,int r){
int k=log_2[r-l+1];
return glca(st[k][l],st[k][r-(1<<k)+1]);
}
vector<int> vec[N];pii q[N];
struct ODT{
int l,r;mutable int v;
ODT(const int &tl,const int &tr,const int &tv){l=tl;r=tr;v=tv;}
friend bool operator<(ODT a,ODT b){return a.l<b.l;}
};
set<ODT> odt;
typedef set<ODT>::iterator iter;
iter split(int x){
iter it=--odt.upper_bound((ODT){x,0,0});
if(it->l==x)return it;
assert(it!=odt.end());
int l=it->l,r=it->r,v=it->v;
odt.erase(it);
odt.insert((ODT){l,x-1,v});
return odt.insert((ODT){x,r,v}).first;
}
void assign(int l,int r,int tim){
iter itr=split(r+1),itl=split(l);
for(iter it=itl;it!=itr;it++)T.add(m-it->v+1,-(it->r-it->l+1));
odt.erase(itl,itr);
odt.insert((ODT){l,r,tim});
T.add(m-tim+1,r-l+1);
}
void upd(int u,int tim){
while(u){
assign(dfn[top[u]],dfn[u],tim);
u=fa[top[u]];
}
}
int main(){
scanf("%d%d%d",&n,&m,&Q);odt.insert((ODT){1,n,0});
for(int i=1,u,v;i<n;i++)scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
for(int i=1;i<=m;i++)scanf("%d",&a[i]),st[0][i]=a[i];
dfs1(1,0),dfs2(1,1);
for(int i=2;i<=m;i++)log_2[i]=log_2[i>>1]+1;
for(int i=1;i<=17;i++)for(int j=1;j+(1<<i)-1<=m;j++)st[i][j]=glca(st[i-1][j],st[i-1][j+(1<<i-1)]);
for(int i=1;i<=Q;i++)cin>>q[i].first>>q[i].second,vec[q[i].second].eb(i),ans[i]=-d[qlca(q[i].first,q[i].second)]+d[1];
for(int i=1;i<=m;i++){
upd(a[i],i);
for(int j:vec[i])ans[j]+=T.ask(m-q[j].first+1);
}
for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现