2019河南省CCPC 树上逆序对

题目连接: 树上逆序对.

题目描述:
一天,Chika 在研究关于所谓的树上逆序对的问题,你能帮助她吗?她会给你一棵有根树,这棵树有 n 个 结点,被编号为 1~ n,1 号结点是根。每个点有一个权值,i 号结点的权值为 a[i]。如果 u 是 v 的祖先结点, 并且 a[u] > a[v],那么 (u,v) 被称作一个“** 逆序对 **”。
Chika 会给你 m 个任务,包含两种类型:
1 u x : 向树中添加一个新结点,其父亲为 u,权值为 x。执行完这个操作后,树的结点总数增加 1,因此该 结点的编号为 n+1(n 为添加这个点之前树中的总结点数)。
2 u : 你需要回答如果删除以 u 为根的子树,树的剩余部分的逆序对数。任意两个类型 2 的任务之间互相 独立。
你需要完成所有任务。
输入:
输入文件的第一行包含两个整数 n (1≤n≤105) 和 m (1≤m≤105),代表树中初始状态的结点总数以及 任务总数。
第二行包含 n 个整数,第 i 个整数是 a[i] (1≤ a[i] ≤109),代表 i 号结点的权值。
第三行包含 n−1 个整数,第 i 个整数是 i+1 号结点的父结点。
接下来是 m 行,每一行描述了一个任务,满足以下两种格式之一:
1 u x
2 u
对于每个任务,u 都满足不小于 1 且不超过该时刻树中结点的总数。对于每个类型 1 的任务,x 都满足 1≤ x ≤109。
你可以在本题的描述中找到任务的含义。

思路
  1. 离线做法,将所有输入数据储存起来,离散化,可以用权值线段树或者树状数组维护离散化之后的权值,用另一个树状数组维护dfs序,里面储存所有逆序点对总和,最后用总共的逆序对个数减去该区间的逆序对个数,在此贴上树状数组+树状数组的做法,线段树方类似,用scanf能过,用cin会T
  2. 也可以在其父节点的基础上建立主席树,
代码

树状数组+主席树

#include<bits/stdc++.h>
#define LLL long long 
#define pi pair<int,LLL>
using namespace std;

const int N=2e5+5;
int temp,n,len,tot;//点总数,离散之后的长度,时间戳 
LLL a[N],ans[N];//每一个点的权值,每一个点的逆序对 
int L[N],R[N];//dfs序列开头和结尾 
int T[N],cnt[N*20],LL[N*20],RR[20*N],t;//主席树 
vector<int>G[N];//邻接表 
vector<LLL>b;//离散数组 
pi p[N];//离线记录的操作 
LLL c[N];//树状数组

inline  int lowbit(int x){return x&(-x);}
inline void add(int x,LLL y){for(;x<=n;x+=lowbit(x))c[x]+=y;}
inline LLL get(int x)
{
	LLL res=0;
	for(;x;x-=lowbit(x))res+=c[x];
	return res;
}

int build(int l,int r)//初始化 
{
	int pos=++t;
	if(l>=r)return pos;
	int mid=(l+r)>>1;
	LL[pos]=build(l,mid);
	RR[pos]=build(mid+1,r);
	return pos;
}
int update(int q,int l,int r,int x)//单点修改 
{
	int pos=++t;
	LL[pos]=LL[q];RR[pos]=RR[q];cnt[pos]=cnt[q]+1;
	if(l>=r)return pos;
	int mid=(l+r)>>1;
	if(x<=mid)LL[pos]=update(LL[q],l,mid,x);
	else RR[pos]=update(RR[q],mid+1,r,x);
	return pos;
}
int query(int q,int l,int r,int x,int y)//区间查询 
{
	if(l>=x&&r<=y)return cnt[q];
	int mid=(l+r)>>1;
	int sum=0,sum1=0;
	if(x<=mid)sum=query(LL[q],l,mid,x,y);
	if(y>mid)sum1=query(RR[q],mid+1,r,x,y);
	return sum+sum1;
}
void dfs(int u,int fa)
{
	L[u]=++tot;//u是父节点时间戳 
	if(u<=temp)//先将原来的加入节点 
	{
	   int pos=lower_bound(b.begin(),b.end(),a[u])-b.begin()+1; 
	   T[u]=update(T[fa],1,len,pos); 
	   ans[u]=query(T[u],1,len,pos+1,len);
	   add(L[u],ans[u]);
	}
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		dfs(v,u); 
	}
	R[u]=tot;
}
int main()
{
	int m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),b.push_back(a[i]);
	int x;
	for(int i=2;i<=n;i++)
	{
	   scanf("%d",&x);
	   G[x].push_back(i);	
	}
	temp=n;
	int id;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&id,&p[i].first);
		if(id==1)
		{
			scanf("%lld",&p[i].second);
			n++;
			G[p[i].first].push_back(n);
			b.push_back(p[i].second);
			a[n]=p[i].second;
		}
	}
	sort(b.begin(),b.end());
	b.erase(unique(b.begin(),b.end()),b.end());//离散化 
	len=b.size();
	T[0]=build(1,len);//初始化第一个树 
	dfs(1,0);
	int num=temp;
	for(int i=1;i<=m;i++)
	{
		if(p[i].second)
		{
			num++;
			int pos=lower_bound(b.begin(),b.end(),a[num])-b.begin()+1; 
			T[num]=update(T[p[i].first],1,len,pos);//增加该点 
			ans[num]+=query(T[num],1,len,pos+1,len);//查询 
			add(L[num],ans[num]);//加入树状数组 
		}
		else printf("%lld\n",get(n)-(get(R[p[i].first])-get(L[p[i].first]-1)));//总共的个数减去该子树中的个数 
	}
	return 0;
}

树状数组+线段树

#include<bits/stdc++.h>
#define LL long long
#define pi pair<int,LL>
using namespace std;
const int N=1e5+5;
LL a[N*2],ans[N*2],c[N*2];
int len,n;//离散化之后的长度 
int cnt[N<<2];//权值线段树所记录当前该点的个数 
vector<int>G[N*2];
vector<LL>b;
int L[N*2],R[N*2],tot;
pi p[N];
/*
   树状数组维护dfs序+普通线段树+二分 +离线 +离散化 
*/ 
inline int lowbit(int x)
{
	return x&(-x);
}
inline LL get(int x)
{
	LL res=0;
	for(;x;x-=lowbit(x))res+=c[x];
	return res;
}
inline void add(int x,LL y)
{
	for(;x<=n;x+=lowbit(x))c[x]+=y; 
}
int query(int t,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)return cnt[t];
	int sum=0,sum1=0;
	int mid=(l+r)>>1;
	if(x<=mid)sum=query(t<<1,l,mid,x,y);
	if(y>mid)sum1=query(t<<1|1,mid+1,r,x,y);
	return sum+sum1;
}
void update(int t,int l,int r,int x,int y)
{
	cnt[t]+=y;
	if(l>=r)return ;
	int mid=(l+r)>>1;       
	if(x<=mid)update(t<<1,l,mid,x,y);
	else update(t<<1|1,mid+1,r,x,y);
}
void dfs(int u)
{
	L[u]=++tot;//记录dfs序 
	int pos=lower_bound(b.begin(),b.end(),a[u])-b.begin()+1;//查询该点的位置 
	ans[u]=query(1,1,len,pos+1,len);//加上当前所有元素中大于该点的个数 
	update(1,1,len,pos,1);//将该点加入到线段树 
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		dfs(v);
	}
	update(1,1,len,pos,-1);//遍历所有的子树之后再将该点从线段树中删除防止影响后面的子节点 
	R[u]=tot;
}
int main()
{
	int m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		b.push_back(a[i]);
	}
	int x;
	for(int i=2;i<=n;i++)
	{
		scanf("%d",&x);
		G[x].push_back(i);
	}
	int temp=n;
	int id;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&id,&p[i].first);
		if(id==1)
		{
			scanf("%lld",&p[i].second);
			b.push_back(p[i].second);
			n++;
			G[p[i].first].push_back(n);
			a[n]=p[i].second;
			p[i].first=n;
			//cout<<"666---";
		}
	} 
	sort(b.begin(),b.end());
	b.erase(unique(b.begin(),b.end()),b.end());//离散化 
	len=b.size();
	//build(1,len); 
	dfs(1);
	for(int i=1;i<=temp;i++) add(L[i],ans[i]);
	for(int i=1;i<=m;i++)
	{
		if(p[i].second)
		{
			add(L[p[i].first],ans[p[i].first]);
		}
		else printf("%lld\n",get(tot)-(get(R[p[i].first])-get(L[p[i].first]-1)));//总的个数减去dfs序中的个数 
	}
	return 0;
}

树状数组+树状数组

#include<bits/stdc++.h>
#define LL long long
#define pi pair<int,LL>
using namespace std;
const int N=1e5+5;
int n,len;//节点个数,离散化之后的点 
int L[N*2],R[N*2],tot;//记录树状数组中维护的dfs序列 
LL a[N*2],ans[N*2];//树状数组储存答案 
vector<int>G[N*2];
vector<LL>b;//储存邻接表 ,储存要加的点 
pi p[N];
/*
   树状数组维护dfs序+树状数组维护权值+二分 +离线 +离散化 
*/ 
inline int lowbit(int x)
{
	return x&(-x);
}
inline void add(LL c[],int nn,int x,int y)
{
	for(;x<=nn;x+=lowbit(x))c[x]+=y;
}
inline LL get(LL c[],int x)
{
	LL res=0;
	for(;x;x-=lowbit(x))res+=c[x];
	return res;
}
LL c[N*2],c1[N*2]; 
void dfs(int u,int fa)
{
    L[u]=++tot;
	int pos=lower_bound(b.begin(),b.end(),a[u])-b.begin()+1;
	ans[u]=get(c1,len)-get(c1,pos);
	add(c1,len,pos,1);
	
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==fa)continue;
		dfs(v,u);
	}
	add(c1,len,pos,-1);
	R[u]=tot;
}

int main()
{
	int m;
	scanf("%d%d",&n,&m); 
	int temp=n;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		b.push_back(a[i]);
	}
	for(int i=2;i<=n;i++)
	{
		int x;
		cin>>x;
		G[x].push_back(i);
	}
	int id;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%lld",&id,&p[i].first);
		if(id==1)
		{
			scanf("%lld",&p[i].second);
			G[p[i].first].push_back(++n);
			b.push_back(p[i].second);
			a[n]=p[i].second;
			p[i].first=n;
		}
	}
	sort(b.begin(),b.end());
	b.erase(unique(b.begin(),b.end()),b.end());
	len=b.size();
	dfs(1,-1);
	for(int i=1;i<=temp;i++)//将最原始的数组的逆序点对数插入 
	  add(c,tot,L[i],ans[i]);
    for(int i=1;i<=m;i++)
    {
    	if(p[i].second)
    	{
    		add(c,tot,L[p[i].first],ans[p[i].first]);
		}//结果等于以该点为底的逆序对减去子树所在区间的和 
		else printf("%lld\n",get(c,tot)-(get(c,R[p[i].first])-get(c,L[p[i].first]-1)));
	}
	return 0;
}

三种方式效率都挺高的

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值