[洛谷P6859]蝴蝶与花

题目

传送门 to luogu

思路

这思路真是绝了……我不知道该怎么说。

注意到左端点需要最小,所以我们一来先猜 l = 1 l=1 l=1 。假如我们求出一个最小的 r r r 使得 [ l , r ] [l,r] [l,r] 中所有数的和超过 k k k ,那么这个和 最多是 k + 1 k+1 k+1

同时,如果说 [ l , r ] [l,r] [l,r] 中所有数的和为 k + 1 k+1 k+1 ,那么 [ l , r − 1 ] [l,r-1] [l,r1] 的和一定是 k − 1 k-1 k1 。故 a r = 2 a_r=2 ar=2此时我们考虑将 l l l 增大一。如果 a l = 1 a_l=1 al=1 那么 [ l + 1 , r ] [l+1,r] [l+1,r] 已经是答案了。否则 [ l + 1 , r + 1 ] [l+1,r+1] [l+1,r+1] 的和是不小于 k k k 的。什么情况下这个和是 k + 1 k+1 k+1 呢?就是 a r + 1 = 2 a_{r+1}=2 ar+1=2 的时候。

可以看到,一个区间 [ l , r ] [l,r] [l,r] 在满足 a r = 2 a_r=2 ar=2 k − 1 = ∑ i = l r − 1 a i k-1=\sum_{i=l}^{r-1}a_i k1=i=lr1ai 时,可以分成三种情况:

  • a l = 1 a_l=1 al=1 。此时 [ l + 1 , r ] [l+1,r] [l+1,r] 即为答案。
  • a l = 2 a_l=2 al=2 a r + 1 = 1 a_{r+1}=1 ar+1=1 。此时 [ l + 1 , r + 1 ] [l+1,r+1] [l+1,r+1] 即为答案。
  • a l = a r + 1 = 2 a_l=a_{r+1}=2 al=ar+1=2 。此时令 l ′ = l + 1 ,    r ′ = r + 1 l'=l+1,\;r'=r+1 l=l+1,r=r+1 ,对 [ l ′ , r ′ ] [l',r'] [l,r] 递归执行分类。

显然只有一种情况要递归,我们只要能将其加速就好了。这种情况我们只需要问, l / r l/r l/r 开始的连续的 2 2 2 有多少个?可以树状数组(或者线段树)上二分。求出第一个 r r r 同样方法。复杂度 O [ ( n + m ) log ⁡ n ] \mathcal O[(n+m)\log n] O[(n+m)logn]

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline int qkpow(int_ b,int_ q,int Mod){
	int ans = 1;
	for(; q; q>>=1,b=b*b%Mod)
		if(q&1) ans = ans*b%Mod;
	return ans;
}

const int MaxN = 21;
int bit[1<<MaxN], n, m, a[1<<MaxN];
void modify(int id,int x){
	for(int i=id; i<=n; i+=(i&-i))
		bit[i] += x;
}
int query(int id){
	int res = 0;
	for(int i=id; i; i-=(i&-i))
		res += bit[i];
	return res;
}

/** @return min_{sum(1,x)>=s} x*/
int findS(int s){
	int x = 0, now = 0;
	for(int j=MaxN-1; j>=0; --j)
		if((1<<j)+x <= n
		&& bit[x+(1<<j)]+now < s)
			now += bit[x += (1<<j)];
	return x+1; // x 是严格小于
}
/** @return max_{sum(id,x)=2(x-id+1)} x */
int countTwo(int id){
	int x = 0, now = -query(id-1);
	for(int j=MaxN-1; j>=0; --j)
		if((1<<j)+x <= n) // 没有爆
		if((1<<j)+x < id // 不超过 id 那就没事了
		|| bit[x+(1<<j)]+now == 2*((1<<j)+x-id+1))
			now += bit[x += (1<<j)];
	return x;
}

char zhihucuan[1<<10];
int main(){
	n = readint(), m = readint();
	for(int i=1; i<=n; ++i)
		modify(i,a[i] = readint());
	while(m --){
		scanf("%s",zhihucuan);
		char opt = *zhihucuan;
		if(opt == 'C'){
			int t = readint();
			modify(t,-a[t]);
			modify(t,a[t] = readint());
		}
		if(opt == 'A'){
			int s = readint();
			int r = findS(s);
			if(r == n+1 || !s){
				puts("none");
				continue;
			}
			if(query(r) == s){
				printf("1 %d\n",r);
				continue;
			}
			if(a[1] == 1){ // 去头即可
				printf("2 %d\n",r);
				continue;
			}
			int cnt1 = countTwo(1);
			int cnt2 = countTwo(r);
			if(cnt1 < cnt2-r+1) // 遇到了可以去头的地方!
				printf("%d %d\n",cnt1+2,r+cnt1);
			else{ // 暴力后移即可
				if(cnt2 == n) puts("none");
				else printf("%d %d\n",cnt2-r+2,cnt2+1);
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值