2020 浙江省赛 E. Easy DP Problem(主席树)

题目链接:http://codeforces.com/gym/102770/problem/E

2020 浙江省赛 E. Easy DP Problem

题意

        题目给出一个dp的方程,然后给出q个询问,每个询问有三个变量l,r,k,选择a[l ~ r]作为b数组,问 d p [ r − l + 1 ] [ k ] dp[ r − l + 1][k] dp[rl+1][k]的值是多少。

思路

  • 经过简单的模拟分析,容易发现最终的结果为 a l . . . . . . a r a_l......a_r al......ar中前k大值之和加上 1 2 + 2 2 + . . . . . + ( r − l + 1 ) 2 1^2+2^2+.....+(r-l+1)^2 12+22+.....+(rl+1)2
  • 根据上述结论,采用主席树的方式维护区间前k大项之和即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;

int n,tot;
ll sumi[maxn];
int a[maxn],root[maxn];
vector<int> nums;

struct node{
	int l,r;
	int cnt;//这个区间内数的个数
	ll sum;//这个区间内数字之和
}tree[maxn<<5];

int geiId(int x)
{
	return lower_bound(nums.begin(),nums.end(),x,greater<int>())-nums.begin();
}

void PushUp(int rt)
{
	tree[rt].cnt=tree[tree[rt].l].cnt+tree[tree[rt].r].cnt;
	tree[rt].sum=tree[tree[rt].l].sum+tree[tree[rt].r].sum;
}
//初始化数据
void init()
{
	for(int i=0;i<=tot;i++){
		tree[i]={0,0,0,0};
	}
	nums.clear();
	for(int i=0;i<=n;i++) root[i]=0;
	tot=0;
}

int Build(int l,int r)
{
	int rt=++tot;
	tree[rt].l=l;
	tree[rt].r=r;
	if(l==r) return rt;
	int mid=(l+r)>>1;
	tree[rt].l=Build(l,mid);
	tree[rt].r=Build(mid+1,r);
	return rt;
}

int Update(int p,int l,int r,int x)
{
	int rt=++tot;
	tree[rt]=tree[p];
	if(l==r){
		tree[rt].cnt++;
		tree[rt].sum+=nums[x];
		return rt;
	}
	int mid=(l+r)>>1;
	if(x<=mid) tree[rt].l=Update(tree[p].l,l,mid,x);
	else tree[rt].r=Update(tree[p].r,mid+1,r,x);
	PushUp(rt);
	return rt;
}

ll Query(int rt,int p,int l,int r,int k)
{
	//为了防止有重复数字,一定要这么处理
	if(l==r){
		return (tree[rt].sum-tree[p].sum)/(tree[rt].cnt-tree[p].cnt)*k;
	}
	int cnt=tree[tree[rt].l].cnt-tree[tree[p].l].cnt;
	int mid=(l+r)>>1;
	if(k<=cnt) return Query(tree[rt].l,tree[p].l,l,mid,k);
	else return tree[tree[rt].l].sum-tree[tree[p].l].sum+Query(tree[rt].r,tree[p].r,mid+1,r,k-cnt);
}

int main()
{
	//预处理1方加到i方的值
	for(ll i=1;i<maxn;i++) sumi[i]=i*i+sumi[i-1];
	int t;
	scanf("%d",&t);
	while(t--){
		init();
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			nums.push_back(a[i]);
		}
		//离散化处理
		sort(nums.begin(),nums.end(),greater<int>());
		nums.erase(unique(nums.begin(),nums.end()),nums.end());

		root[0]=Build(0,nums.size()-1);
		for(int i=1;i<=n;i++){
			root[i]=Update(root[i-1],0,nums.size()-1,geiId(a[i]));
		}
		
		int q;
		scanf("%d",&q);
		while(q--){
			int l,r,k;
			scanf("%d %d %d",&l,&r,&k);
			int m=r-l+1;
			printf("%lld\n",sumi[m]+Query(root[r],root[l-1],0,nums.size()-1,k));
		}
	}
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值