zoukankan      html  css  js  c++  java
  • 题解 LOJ2049 「HNOI2016」网络

    题目链接

    简要题意:给定一棵树。让你支持三种操作:

    1. 加入一条路径((u,v))。路径有一个权值(w)
    2. 删除之前某一时刻加入的路径。
    3. 对树上某个节点(u)询问:问当前所有不经过(u)的路径的权值的最大值。

    可以发现,本题的难点在于最大值,它不像类似于“求权值和”这样的问题,它不支持撤销,也就是没有“可减性”。因此我们很难通过一般的数据结构,完成删除操作,并维护最大值。

    那么换个思路:既然不能直接维护答案,考虑对于每次询问都二分答案。二分之后,求最大值的问题,就转化为了数路径数量的问题。具体来说,设二分值为( ext{mid})。那么考虑所有权值大于( ext{mid})的路径。如果这些路径全部覆盖了询问点(u),则该询问的答案小于等于( ext{mid}),否则该询问的答案大于( ext{mid})

    加入、删除一条路径。以加入为例。可以令路径的两个端点点权(+1),LCA和LCA的父亲点权(-1),则经过某个节点(u)的路径数量,就是(u)子树内所有节点的点权和。求出dfs序后,问题转化为单点修改、区间求和。可以用树状数组实现,常数很小。

    如果对每次询问都二分答案,然后暴力加入这些路径,则单次询问的时间复杂度达到(O(nlog^2n)),总时间复杂度(O(mnlog^2n)),无法承受。

    注意到,对于任何一个询问,假设二分值为( ext{mid}),要考虑的都是权值大于( ext{mid})的这些路径。也就是说,只要二分值相同,对于不同的询问,我们在树状数组上加入的是同一些路径!于是我们考虑把这样的加入路径的操作放在一起做。简单讲,我们之前的做法是:对每个询问,求出二分值,做一遍加入路径的操作;现在变成:对每个二分值,加入对应的路径,然后一次性处理所有对应的询问。这就是整体二分。

    我们把它写成一个分治的过程。每次分治传入的参数是两个东西:(1) 当前二分的答案区间([l,r]),(2) 这个区间对应的操作。这些操作又分为两种,一是路径权值在([l,r])之间的插入、删除操作,二是我们已经确定其答案在([l,r])之间的询问操作。每次传入的这些操作在传入时是按时间顺序排好的。我们只要按顺序处理它们,然后将其分配到左半边、右半边去即可。

    时间复杂度(O(mlog^2n))

    参考代码(在LOJ查看):

    //problem:LOJ2049
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    const int MAXN=1e5,MAXM=2e5;
    int n,m;
    struct EDGE{int nxt,to;}edge[MAXN*2+5];
    int head[MAXN+5],tot;
    inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
    int fa[MAXN+5],sz[MAXN+5],son[MAXN+5],dep[MAXN+5];
    void dfs1(int u){
    	sz[u]=1;
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(v==fa[u])continue;
    		fa[v]=u;
    		dep[v]=dep[u]+1;
    		dfs1(v);
    		sz[u]+=sz[v];
    		if(!son[u]||sz[v]>sz[son[u]])son[u]=v;
    	}
    }
    int top[MAXN+5],dfn[MAXN+5],ofn[MAXN+5],rev[MAXN+5],cnt_dfn;
    void dfs2(int u,int t){
    	top[u]=t;
    	dfn[u]=++cnt_dfn;
    	rev[cnt_dfn]=u;
    	if(son[u])dfs2(son[u],t);
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(v==fa[u]||v==son[u])continue;
    		dfs2(v,v);
    	}
    	ofn[u]=cnt_dfn;
    }
    int get_lca(int u,int v){
    	while(top[u]!=top[v]){
    		if(dep[top[u]]<dep[top[v]])swap(u,v);
    		u=fa[top[u]];
    	}
    	return (dep[u]<dep[v])?u:v;
    }
    
    struct FenwickTree{
    	int c[MAXN+5];
    	void modify(int p,int x){
    		for(;p<=n;p+=(p&(-p)))c[p]+=x;
    	}
    	int query(int p){
    		int res=0;
    		for(;p;p-=(p&(-p)))res+=c[p];
    		return res;
    	}
    	int query(int l,int r){
    		return query(r)-query(l-1);
    	}
    	FenwickTree(){}
    }T;
    
    void path_modify(int u,int v,int x){
    	T.modify(dfn[u],x);
    	T.modify(dfn[v],x);
    	int lca=get_lca(u,v);
    	T.modify(dfn[lca],-x);
    	if(fa[lca])T.modify(dfn[fa[lca]],-x);
    }
    
    int ans[MAXM+5];
    struct Query_t{
    	int op,u,v,w,id;
    }q[MAXM+5];
    void solve(int ans_l,int ans_r,int pos_l,int pos_r){
    	if(ans_l==ans_r){
    		for(int i=pos_l;i<=pos_r;++i)if(q[i].op==2)ans[q[i].id]=ans_l;
    		return;
    	}
    	static Query_t que_l[MAXM+5],que_r[MAXM+5];
    	int mid=(ans_l+ans_r)>>1;
    	int cnt_l=0,cnt_r=0,sum=0;
    	bool have_query_l=false,have_query_r=false;
    	for(int i=pos_l;i<=pos_r;++i){
    		if(q[i].op==2){
    			if(T.query(dfn[q[i].u],ofn[q[i].u])==sum){
    				que_l[++cnt_l]=q[i];
    				have_query_l=true;
    			}
    			else{
    				que_r[++cnt_r]=q[i];
    				have_query_r=true;
    			}
    		}
    		else{
    			if(q[i].w<=mid){
    				que_l[++cnt_l]=q[i];
    			}
    			else{
    				int x=(q[i].op==0?1:-1);
    				path_modify(q[i].u,q[i].v,x);
    				sum+=x;
    				que_r[++cnt_r]=q[i];
    			}
    		}
    	}
    	for(int i=pos_l;i<=pos_r;++i){
    		if(q[i].op!=2&&q[i].w>mid){
    			int x=(q[i].op==0?1:-1);
    			path_modify(q[i].u,q[i].v,-x);
    		}
    	}//树状数组清空
    	for(int i=1;i<=cnt_l;++i)q[pos_l+i-1]=que_l[i];
    	for(int i=1;i<=cnt_r;++i)q[pos_l+cnt_l+i-1]=que_r[i];
    	if(have_query_l)solve(ans_l,mid,pos_l,pos_l+cnt_l-1);
    	if(have_query_r)solve(mid+1,ans_r,pos_l+cnt_l,pos_r);
    }
    
    int main() {
    	cin>>n>>m;
    	for(int i=1,u,v;i<n;++i)cin>>u>>v,add_edge(u,v),add_edge(v,u);
    	dfs1(1);dfs2(1,1);
    	int max_w=0;
    	for(int i=1;i<=m;++i){
    		cin>>q[i].op;
    		q[i].id=i;
    		if(q[i].op==0){
    			cin>>q[i].u>>q[i].v>>q[i].w;
    			max_w=max(max_w,q[i].w);
    			ans[i]=-3;
    		}
    		else if(q[i].op==1){
    			int t;cin>>t;
    			q[i].u=q[t].u;
    			q[i].v=q[t].v;
    			q[i].w=q[t].w;
    			ans[i]=-3;
    		}
    		else{
    			cin>>q[i].u;
    		}
    	}
    	solve(-1,max_w,1,m);
    	for(int i=1;i<=m;++i)if(ans[i]!=-3)cout<<ans[i]<<endl;
    	return 0;
    }
    
  • 相关阅读:
    Hello World!
    Nginx加权轮询算法
    git常用命令
    linux命令
    sql 表值函数与标量值函数
    数据查询和操纵时连接的打开状态
    插入一条和上一条数据关联的数据
    C# 输出24小时格式时间
    c#中用sql存储过程
    AndroidManifest.xml文件解析
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12893622.html
Copyright © 2011-2022 走看看