牛牛的凑数游戏详解

基础思路:

考虑一个有序序列,有可重集A包含 [ l , r ] [l,r] [l,r]内所有数(记作 A = [ l , r ] A=[l,r] A=[l,r]
若我们有,B= [ l , r − 1 ] [l,r-1] [l,r1]可表示的数为 [ 1 , s u m B ] [1,sumB] [1,sumB]sumB为B中所有元素和),我们说B是理想的可重集(表示区间是连续的,A的最小不能表示数可能继续增大)。
参考背包原理,可以发现新的可表示区间为: [ 1 , s u m B ] ∪ [ 1 + a [ r ] , s u m B + a [ r ] ] [1,sumB]∪[1+a[r], sumB+a[r] ] [1,sumB][1+a[r],sumB+a[r]]

尝试用B推出A。
①新的区间是连续的:答案是sumB+a[r]。A是新的理想可重集。
②一旦区间不连续:A和 [ l , r 到 n ] [l, r到n] [l,rn]的最小不能表示数都是sumB+1,(因为序列有序,最小的可选数就是现在的,更大的数)

朴素:

对于一个区间,排序后计算第一个sum[i] + 1 < a[i + 1]

正解:

考虑一个用可持久化线段树处理过值域的区间。
现在我们知道该区间某个值域的所有数的和,考虑优化上述算法:
设:当前处理的理想区间的和为SUM,最大数为X,那么待选数是大于X的,所有小于SUM+1的数是不会使区间产生裂缝的。 因此可以直接把 [ X , S U M + 1 ] [X,SUM+1] [X,SUM+1]的数全部一次性加入理想区间里。

复杂度证明

容易发现理想可重集的可表示区间的长度是倍增的。
s i z e o f [ L , R ] − > s i z e o f [ L , R ] + s i z e o f [ L + a [ R + 1 ] , R + a [ R + 1 ] ] ; sizeof [L, R] -> sizeof[L,R]+sizeof[L+a[R+1], R+a[R+1]]; sizeof[L,R]>sizeof[L,R]+sizeof[L+a[R+1],R+a[R+1]];

实现:

附上代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>

using namespace std;
typedef long long xt;
#define R(i) for(int i=1; i<=n; ++i)
#define rep(i, x, y) for(int i=x; i<=y; ++i)
const int N=1e5+10, SI=1e7;
const int D=1e9+10;
int n,m;
int a[N];

int amt, rt[SI];
struct nod {
	int v;
	int ls, rs;
	int ihs(const nod &x) {
		ls = x.ls;
		rs = x.rs;
	}
}tr[SI];
#define lc tr[p].ls
#define rc tr[p].rs
#define calmid \
int mid=(L+R)>>1;
int cha(int f, int L, int R, int x, int d) {
	int p=++amt;
	tr[p].v = tr[f].v+d;
	tr[p].ihs(tr[f]);
	if(L==R) return p;
	else {
		calmid;
		if(x<=mid) lc=cha(lc, L, mid, x, d);
		else rc=cha(rc, mid+1, R, x, d);
	}
	return p;
}
xt iqr(int p, int L, int R, int l, int r) {
	int c=0;
	if(!p) return 0;
	if(l<=L&&R<=r) return tr[p].v;
	else {
		calmid;
		if(l<=mid) c+=1ll*iqr(lc, L, mid, l, r);
		if(mid+1<=r) c+=1ll*iqr(rc, mid+1, R, l, r);
	}
	return c;
}
int calc(int l, int r) {
	int x=0, s=0, tmp=0;
	while(tmp=(iqr(rt[r],1,D,x+1,s+1) - iqr(rt[l-1],1,D,x+1,s+1))) {
		x=s+1; s+=tmp;
	}
	return s+1;
}
int in() {
	int nt, x=0;
	while(!isdigit(nt=getchar())); x=(nt^'0');
	while(isdigit(nt=getchar())) x=(x<<1)+(x<<3)+(nt^'0');
	return x;
}
int main() {
	freopen("niuniunum.in", "r", stdin);
	freopen("niuniunum.out", "w", stdout);
	int x, y;
	scanf("%d", &n);
	for(int i=1; i<=n; ++i) x=in(), rt[i]=cha(rt[i-1],1,D,x,x);
	scanf("%d", &m);
	for(int i=1; i<=m; ++i) {
		x=in(), y=in();
		printf("%d\n", calc(x, y));
	}
	
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值