zoukankan      html  css  js  c++  java
  • [CSP校内集训]ginkgo(树上启发式合并/逆序对)

    题意

    给一棵点带权的树,对于每个点,求其子树中有多少个点的权值 大/小/等于它(,(nleq 200000))

    解法1

    看到数据范围没多想就写了个树上启发式合并,太裸了。。。

    维护一棵值域线段树,先跑轻儿子再跑重儿子,删轻儿子不删重儿子,自底向上求解即可,时间复杂度为(O(nlog^2n))

    Code

    #include<bits/stdc++.h>
    #define N 200005
    #define Min(x,y) ((x)<(y)?(x):(y))
    #define Max(x,y) ((x)>(y)?(x):(y))
    using namespace std;
    typedef long long ll;
    int n,a[N],b[N],len;
    int size[N],son[N];
    int A[N],B[N],C[N];
    int sum[N<<2];
    struct Edge
    {
    	int next,to;
    }edge[N<<1];int head[N],cnt=1;
    void add_edge(int from,int to)
    {
    	edge[++cnt].next=head[from];
    	edge[cnt].to=to;
    	head[from]=cnt;
    }
    
    template <class T>
    void read(T &x)
    {
    	char c; int sign=1;
    	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
    }
    void modify(int rt,int l,int r,int x,int val)
    {
    	if(l==r) return (void)(sum[rt]+=val);
    	int mid=(l+r)>>1;
    	if(x<=mid) modify(rt<<1,l,mid,x,val);
    	else modify(rt<<1|1,mid+1,r,x,val);
    	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    int query(int rt,int l,int r,int x,int y)
    {
    	if(x>y) return 0;
    	if(x<=l&&r<=y) return sum[rt];
    	int mid=(l+r)>>1;
    	int ret=0;
    	if(x<=mid) ret+=query(rt<<1,l,mid,x,y);
    	if(y>mid) ret+=query(rt<<1|1,mid+1,r,x,y);
    	return ret;
    }
    void DFS(int rt,int fa)
    {
    	size[rt]=1;
    	for(int i=head[rt];i;i=edge[i].next)
    	{
    		int v=edge[i].to;
    		if(v==fa) continue;
    		DFS(v,rt);
    		size[rt]+=size[v];
    		if(size[v]>size[son[rt]]) son[rt]=v;
    	}
    }
    void clr(int rt,int fa,int val)
    {
    	modify(1,1,len,a[rt],val);
    	for(int i=head[rt];i;i=edge[i].next)
    	{
    		int v=edge[i].to;
    		if(v==fa) continue;
    		clr(v,rt,val);
    	}
    }
    void dfs(int rt,int fa)
    {
    	for(int i=head[rt];i;i=edge[i].next)
    	{
    		int v=edge[i].to;
    		if(v==fa || v==son[rt]) continue;
    		dfs(v,rt);
    		clr(v,rt,-1);
    	}
    	if(son[rt]) dfs(son[rt],rt);
    	for(int i=head[rt];i;i=edge[i].next)
    	{
    		int v=edge[i].to;
    		if(v==fa || v==son[rt]) continue;
    		clr(v,rt,1);
    	}
    	A[rt]=query(1,1,len,1,a[rt]-1);
    	B[rt]=query(1,1,len,a[rt],a[rt]);
    	C[rt]=query(1,1,len,a[rt]+1,len);
    	modify(1,1,len,a[rt],1);
    }
    
    int main()
    {
    	freopen("ginkgo.in","r",stdin);
    	freopen("ginkgo.out","w",stdout);
    	read(n);
    	for(int i=2;i<=n;++i)
    	{
    		int x; read(x);
    		add_edge(x,i);
    		add_edge(i,x);
    	}
    	for(int i=1;i<=n;++i) read(a[i]),b[++len]=a[i];
    	sort(b+1,b+len+1); len=unique(b+1,b+len+1)-b-1;
    	for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+len+1,a[i])-b;
    	DFS(1,0);
    	dfs(1,0);
    	for(int i=1;i<=n;++i) printf("%d %d %d
    ",A[i],B[i],C[i]);
    	return 0;
    }
    

    解法2

    只考虑怎么求小于的情况(大于的情况反过来求,等于的情况直接全集-小于-大于即可)

    用dfs序将树拍成序列,对权值从小到大排序,对一个点查询子树就是查询一个区间的权值,然后再将它加入这个序列中,用个树状数组即可

    时间复杂度(O(nlogn)),代码很简单就不贴了(其实是没写

    本题维护值域和维护序列皆可

  • 相关阅读:
    判断一个大于1的数是不是一个素数
    luogu P3241 [HNOI2015]开店
    换根DP
    CF1187E Tree Painting
    luogu P3345 [ZJOI2015]幻想乡战略游戏
    费用流
    FZOJ 4112 脱单计划
    计数DP
    FZOJ 4109 青青草原的表彰大会
    状压DP
  • 原文地址:https://www.cnblogs.com/Chtholly/p/11805193.html
Copyright © 2011-2022 走看看