Codeforces 840D Destiny 【主席树】

题目地址:点击打开链接

先考虑问题的简化版。假设l和r固定不变,由于数字范围同样是1到n,故我们可以考虑用线段树维护一个计数表(统计1有多少个,2有多少个,3有多少个,。。。),每个点的权值表示值在区间[l,r]之间的数的个数。

那么对于每次查询的k,先计算出((r-l+1)/k)+1(记为P),那么目标就是从线段树中找出权值大于等于P的最左边的叶子节点。

直接从线段树的顶部开始往下搜,如果搜到最底部的话便返回当前叶子节点代表的数值,否则如果左边分支的权值大于等于P则往左边搜下去,如果左边分支的权值小于P或者左边搜下去的返回值为-1,则往右边分支搜(如果右边分支的权值大于等于P的话)。如果两边都搜不到结果或者小于P的话则返回-1,否则优先返回左边搜到的结果。

可以通过极端情况推导的方法(或者强有力的直觉)来得知,在2<=k<=5的范围内,每次查询的时间复杂度差不多是O(logn)。

而对于原问题而言,只需要按照数列的顺序从左到右维护一棵主席树,然后每次要查询的线段树便是第r次更新的线段树减去第l-1次更新的线段树。

代码如下:

#include <bits/stdc++.h>

using namespace std;

#define sfi(a) scanf("%d",&a)
#define sfd(a) scanf("%lf",&a)
#define sfl(a) scanf("%lld",&a)
#define sfs(a) scanf("%s",a)

#define rep(i,a,b) for(int i=int(a);i<int(b);++i)
#define dwn(i,b,a) for(int i=int(b-1);i>=int(a);--i)

#define mem(a,p) memset(a,p,sizeof(a))

typedef long long LL;
typedef unsigned UINT;
typedef unsigned long long ULL;

struct Chairman_Tree
{
	struct node
	{
		int l,r;
		int cnt;
		void init()
		{
			l=r=-1;
			cnt=0;
		}
	};
	
	node a[7000005];
	int top;
	
	int root[300005];
	int rtop;
	
	int n;
	
	void BuildTree(int l,int r,int p)
	{
		a[p].init();
		if(l==r)return;
		int mid=(l+r)>>1;
		a[p].l=top++;
		BuildTree(l,mid,a[p].l);
		a[p].r=top++;
		BuildTree(mid+1,r,a[p].r);
	}
	
	void init(int nn)
	{
		n=nn;
		top=1;
		BuildTree(1,n,0);
		rtop=1;
		root[0]=0;
	}
	
	void Add(int e)
	{
		a[top].init();
		a[top].cnt=a[root[rtop-1]].cnt+1;
		
		root[rtop]=top;
		top++;rtop++;
		
		int p1=root[rtop-2],p2=root[rtop-1];
		int l=1,r=n;
		while(r-l)
		{
			int mid=(l+r)>>1;
			if(e<=mid)
			{
				a[p2].r=a[p1].r;
				a[p2].l=top;
				top++;
				p2=a[p2].l;
				p1=a[p1].l;
				a[p2].init();
				a[p2].cnt=a[p1].cnt+1;
				
				r=mid;
			}
			else
			{
				a[p2].l=a[p1].l;
				a[p2].r=top;
				top++;
				p2=a[p2].r;
				p1=a[p1].r;
				a[p2].init();
				a[p2].cnt=a[p1].cnt+1;
				
				l=mid+1;
			}
		}
	}
	
	int DFS(int p1,int p2,int cnt,int l,int r)
	{
		if(a[p2].cnt-a[p1].cnt<cnt)return -1;
		if(l==r)return l;
		int mid=(l+r)>>1;
		int ans=DFS(a[p1].l,a[p2].l,cnt,l,mid);
		if(ans==-1)ans=DFS(a[p1].r,a[p2].r,cnt,mid+1,r);
		return ans;
	}
	
	int Query(int l,int r,int k)
	{
		int p1=root[l-1],p2=root[r];
		int cnt=((r-l+1)/k)+1;
		return DFS(p1,p2,cnt,1,n);
	}
	
	void Print()
	{
		printf("******************************\n");
		printf("RootList: ");
		rep(i,0,rtop)printf("%d ",root[i]);
		printf("\n");
		rep(i,0,top)
		{
			printf("%d: \tl=%d \tr=%d \tcnt=%d\n",i,a[i].l,a[i].r,a[i].cnt);
		}
		printf("******************************\n");
	}
}C;

int main()
{
	int n,q;
	while(scanf("%d%d",&n,&q)==2)
	{
		C.init(n);
		rep(i,0,n)
		{
			int p;
			sfi(p);
			C.Add(p);
		}
		rep(i,0,q)
		{
			int l,r,k;
			scanf("%d%d%d",&l,&r,&k);
			printf("%d\n",C.Query(l,r,k));
		}
	}
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值