专栏文章
题解:P14411 [JOISC 2015] 道路建设 / Road Development
P14411题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @min4f2bw
- 此快照首次捕获于
- 2025/12/01 20:23 3 个月前
- 此快照最后确认于
- 2025/12/01 20:23 3 个月前
本题解采用了P11361 [NOIP2024] 编辑字符串的一篇高赞题解的形式,采用循序渐进的方法带领读者走向正解。
若你完全没有思路:
提示
想一想:两种操作的本质分别是什么?
答案
第一种操作:查询 到 路径上满足某种条件(未铺设路面)的边的数量。
第二种操作:判断 和 是否联通。
有一些想法后:
提示
若要建图,图是什么样的?
答案
在任意时刻,图为森林且不存在环。
证明
对于第一种操作,若原先不存在 到 的路径,那么会连接一条 到 的路径使它们联通,若原先存在,则不会建边而是修改路径上已铺设路面的数量。
对于第二种操作,它根本不会建边。
成功分析透彻题目后:
提示
如何维护上述两种操作?
答案
对于第一种操作,前面已经说过,在任意时刻图都是森林且不存在环,那么它们路径唯一,带修改且要维护路径一般常用树链剖分。
具体做法:若原先 到 不联通,则联通以后加一条边,然后将这条边权值强制修改成 ,表示这条道路未铺设路面;若原先联通,则把 到 路径上所有的边权值都强制修改成 ,表示这些道路都已经被铺设路面了。但是树链剖分维护的是点权,所以要边权转点权。
对于第二种操作,用一个并查集维护连通性即可。
注意
由于图可能是森林,所以会有很多棵树,要对所有的树都进行树链剖分。
好的,你已经成功切掉了这道蓝题。
AC code
CPP#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define mp(i,j) make_pair(i,j)
const int N=3e5+10;
int n,Q;
struct node{
int op,x,y,val;
}q[N];
int ans[N],F[N];
int findx(int x){
if(x!=F[x])F[x]=findx(F[x]);
return F[x];
}
vector<int> g[N];
int dfn[N],num=0,son[N],siz[N],dep[N],top[N],f[N];
inline void dfs(int u,int fa){
son[u]=0;siz[u]=1;
dep[u]=dep[fa]+1;f[u]=fa;
for(int v:g[u]){
if(v==fa)continue;
dfs(v,u);
siz[u]+=siz[v];
if(!son[u]||siz[son[u]]<siz[v])son[u]=v;
}
}
inline void dfs2(int x,int topx){
top[x]=topx;
dfn[x]=++num;
if(!son[x])return;
dfs2(son[x],topx);
for(int y:g[x]){
if(y!=son[x]&&y!=f[x])dfs2(y,y);
}
return;
}
struct BIT{
int t[N<<2],tag[N<<2];
inline void push_up(int x){
t[x]=t[x*2]+t[x*2+1];
return;
}
inline void push_down(int l,int r,int x){
if(tag[x]!=-1){
int mid=l+r>>1,k=tag[x];
tag[x*2]=k;tag[x*2+1]=k;
t[x*2]=(mid-l+1)*k;t[x*2+1]=(r-mid)*k;
tag[x]=-1;
}
return;
}
inline void build(int l,int r,int x){
tag[x]=-1;t[x]=0;
if(l==r)return;
int mid=l+r>>1;
build(l,mid,x*2);
build(mid+1,r,x*2+1);
return;
}
inline void update(int L,int R,int l,int r,int x,int k){
if(L<=l&&r<=R){
t[x]=(r-l+1)*k;
tag[x]=k;
return;
}
push_down(l,r,x);
int mid=l+r>>1;
if(L<=mid)update(L,R,l,mid,x*2,k);
if(R>mid)update(L,R,mid+1,r,x*2+1,k);
push_up(x);
return;
}
inline int ask(int L,int R,int l,int r,int x){
if(L<=l&&r<=R)return t[x];
push_down(l,r,x);
int mid=l+r>>1,res=0;
if(L<=mid)res+=ask(L,R,l,mid,x*2);
if(R>mid)res+=ask(L,R,mid+1,r,x*2+1);
return res;
}
}tree;
inline void update_range(int x,int y,int k){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
tree.update(max(1ll,dfn[top[x]]-1),dfn[x]-1,1,n,1,k);
x=f[top[x]];
}
if(x==y)return;
else{
if(dep[x]>dep[y])swap(x,y);
tree.update(dfn[x],dfn[y]-1,1,n,1,k);
}
return;
}
inline int ask_range(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res+=tree.ask(max(1ll,dfn[top[x]]-1),dfn[x]-1,1,n,1);
//cout<<"x="<<x<<" top["<<x<<"]="<<top[x]<<" dfn[top[x]]-1="<<dfn[top[x]]-1<<" dfn[x]-1="<<dfn[x]-1<<"\n";
x=f[top[x]];
}
if(x==y)return res;
if(dep[x]>dep[y])swap(x,y);
res+=tree.ask(dfn[x],dfn[y]-1,1,n,1);
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>Q;
for(int i=1;i<=n;i++)F[i]=i;
for(int i=1;i<=Q;i++){
cin>>q[i].op>>q[i].x>>q[i].y;
int fu=findx(q[i].x),fv=findx(q[i].y);
//cout<<"i="<<i<<" fu="<<fu<<" fv="<<fv<<"\n";
if(q[i].op==1){
if(fu==fv)q[i].val=0;//本身就联通不需要加边
else{
F[fu]=fv;q[i].val=1;//不联通,加边然后改成1,查询时查询有多少个1即可
g[q[i].x].push_back(q[i].y);
g[q[i].y].push_back(q[i].x);
//cout<<"u="<<q[i].x<<" v="<<q[i].y<<"\n";
}
}
else if(fu!=fv&&q[i].op==2)ans[i]=-1;
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
dfs(i,0);
dfs2(i,i);
}
}
//for(int i=1;i<=n;i++)cout<<"dfn["<<i<<"]="<<dfn[i]<<" ";cout<<"\n";
for(int i=1;i<=Q;i++){
if(q[i].op==1){
update_range(q[i].x,q[i].y,q[i].val);
//cout<<"i="<<i<<" val="<<q[i].val<<"\n";
}
else{
if(ans[i]==-1)continue;
else ans[i]=ask_range(q[i].x,q[i].y);
}
}
for(int i=1;i<=Q;i++)if(q[i].op==2)cout<<ans[i]<<"\n";
return 0;
}
/*3 7
1 1 2
2 2 1
2 2 3
1 2 1
2 1 2
1 2 3
2 1 3*/
若对你有帮助,也许可以给我点个赞?
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...