P4587 [FJOI2016]神秘数 题解

9 篇文章 0 订阅

传送门:P4587 [FJOI2016]神秘数

主席树

Solution

思路其实就是一楼大佬的思路,只不过大佬写的有些内容我太弱看不懂,所以来补充一下。

1

对于区间 [ l , r ] [l,r] [l,r],将它升序排序后从左往右扫,设当前可以表示出的数为 [ 1 , x ] [1,x] [1,x] a n s ans ans ( x + 1 ) (x + 1) (x+1)

显然,对于当前扫到的数 a i a_i ai,当且仅当它小于等于 a n s ans ans 时,才对 a n s ans ans 有用。

用处即将值域扩大到 [ 1 , x + a i ] [1,x+a_i] [1,x+ai]

因为在原先 [ 1 , x ] [1,x] [1,x] 的基础上我们可以依次得到 ( 1 + a i ) , ( 2 + a i ) , ( 3 + a i ) + ⋯ + ( x + a i ) (1+a_i),(2+a_i),(3+a_i)+\cdots +(x+a_i) (1+ai),(2+ai),(3+ai)++(x+ai)

所以只用考虑每次在区间 [ l , r ] [l,r] [l,r] 中小于等于 a n s ans ans 的数。

2

按照 1 中的思路,若小于等于 a n s ans ans 的数的和 r e s ≥ a n s res\geq ans resans,则一定有未选的且小于等于 a n s ans ans 的数,令 a n s = r e s + 1 ans=res+1 ans=res+1 即可。(大佬原话,不难理解。)

反之则说明了答案就是 a n s ans ans

这样可以使得我们不断去更新 a n s ans ans,直到得出最大的 a n s ans ans

3

按照 2 中的思路,对于每一个查询,我们都要不断去询问区间 [ l , r ] [l,r] [l,r] 中值小于等于 a n s ans ans 的数,直到找到答案。

所以,要用主席树来维护。此题的主席树是对值域的划分。

对于 r o o t i root_i rooti,它的子树维护的是 a 1 a_1 a1 a i a_i ai 中每一个数在值域里的状态。

正因如此,我们才可以查询某一区间内小于等于某数的所有数之和。

具体地,可以看主席树模板:P3834 【模板】可持久化线段树 2,它也是类似的对值域的划分。

Code

#include<bits/stdc++.h>
using namespace std;

#define rep(i, a, b) for(int i = a; i <= b; ++i)
const int maxn = 1e5 + 5;
const int inf = 1e9;
int n;
int a[maxn];
struct node{
	int l, r;
	int sum;
}t[maxn << 5];
int m;
int lt, rtm;
int tot;
int rt[maxn];

inline int addt(int lst, int l, int r, int k, int d)
{
	int nw = ++tot;
	t[nw] = t[lst], t[nw].sum += d;
	if(l == r) return nw;
	int mid = (l + r) >> 1;
	if(k <= mid) t[nw].l = addt(t[lst].l, l, mid, k, d);
	else t[nw].r = addt(t[lst].r, mid + 1, r, k, d);
	return nw;
}

inline int query(int pl, int pr, /*在 a[pl] 到 a[pr] 这一区间中*/int l, int r,/*当前询问范围*/ int ql, int qr/*权值的合法范围*/)
{
	if(ql <= l and qr >= r) return t[pr].sum - t[pl].sum;
	int mid = (l + r) >> 1, res = 0;
	if(ql <= mid) res += query(t[pl].l, t[pr].l, l, mid, ql, qr);
	if(qr > mid) res += query(t[pl].r, t[pr].r, mid + 1, r, ql, qr);
	return res;
}

int main()
{
	scanf("%d", &n);
	rep(i, 1, n) scanf("%d", &a[i]);
	rep(i, 1, n) rt[i] = addt(rt[i - 1], 1, inf, a[i], a[i]);
	scanf("%d", &m);
	rep(i, 1, m)
	{
		scanf("%d%d", &lt, &rtm);
		int ans = 1;
		while(531)
		{
			int tmp = query(rt[lt - 1], rt[rtm], 1, inf, 1, ans);
			if(tmp >= ans) ans = tmp + 1;
			else break;
		}
		printf("%d\n", ans);
	}
	return 0;
}

感谢阅读。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值