P6859 蝴蝶与花

题目传送门

十分好的题,使我的大脑旋转。

思路

刚开始没什么思路,只好从数据范围下手,说 \(a_i = 1/2\),然后数据范围 \(n,m \le 2\times10^6\),有什么用捏。

\(sum\) 为该数组总和,发现如果 \(sum >= s\),那么一定会取到 \(s\)\(s+1\),这个很显然,因为你取得最后一个只有可能是 \(1/2\),而去之前的值只有可能是 \(s-1/s-2\),而若是 \(s-2\) 取到 \(1\) 还会再取,转到 \(s-1\) 的情况了,说以一定是合理的。

若取到 \(s\) 就不管了,否则 \(s+1\) 说明最后一个是 \(2\),我们需要减一个 \(1\) 或减一个 \(2\) 加一个 \(1\)

找到目前二分出来的区间中第一个 \(a_i=1\) 的,然后在找非这个区间外第一个 \(a_j=1\) 的,然后判一下。

容易发现,区间长度固定了就不会变了,通过这个还可以简化代码实现难度。

所以只需要维护区间和,单点更改,同时在记一下 \(1\) 在哪,我用的线段树+set,线段树上二分复杂度单 log,也有其它的,你也可以试试哦。

code

#include<bits/stdc++.h>
#define mid1 ((c[p].l+c[p].r)>>1)
#define ls (p<<1)
#define rs ((p<<1)+1)
using namespace std;
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=200000000;char buf[BUF],to,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 3e6+10;
int n,m,x,y,z,a[N],l,r,o,o1,o2,o3,L,R,L1,R1;
char op; 
struct w
{
	int l,r,dat;
}c[N<<2];
set<int>st;
void build(int p,int l,int r)
{
	c[p].l = l,c[p].r = r;
	if(l == r)
	{
		c[p].dat = a[l];
		return;
	}
	build(ls,l,mid1),build(rs,mid1+1,r);
	c[p].dat = c[ls].dat+c[rs].dat;
}
void change(int p,int l)//单点修改 
{
	if(c[p].l == l && c[p].r == l)
	{
		c[p].dat = a[l];
		return;
	}
	if(l <= mid1) change(ls,l);
	else change(rs,l);
	c[p].dat = c[ls].dat+c[rs].dat;
}
int query(int p,int l,int r)//区间求和 
{
	if(l <= c[p].l && c[p].r <= r) return c[p].dat;
	int ans = 0;
	if(l <= mid1) ans = query(ls,l,r);
	if(mid1 < r) ans += query(rs,l,r);
	return ans;
}
int query(int p,int x)//线段树上二分 
{
	if(x == 0) return 0;
	if(c[p].l == c[p].r) 
	{
		R = max(R,c[p].r);
		return c[p].dat;
	}
	if(c[ls].dat >= x) return query(ls,x);
	else 
	{
		R = max(R,c[ls].r);
		return c[ls].dat+query(rs,x-c[ls].dat);
	}
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	read(n),read(m);
	for(int i = 1;i <= n;i++) 
	{	
		read(a[i]);
		if(a[i] == 1) st.insert(i);
	}
	st.insert(n+100);
	build(1,1,n);
	for(int i = 1;i <= m;i++)
	{
		cin >> op;
		if(op == 'A')
		{
			L = 1,R = -1e9,L1 = 1e9,R1 = -1e9;
			read(x);
			o1 = query(1,x);
			if(x == 0)//特判,没判被肘死了 
			{
				pc('n'),pc('o'),pc('n'),pc('e'),pc('\n');
				continue;
			}
			if(o1 == x) L1 = L,R1 = R;
			else if(o1 < x)
			{
				pc('n'),pc('o'),pc('n'),pc('e'),pc('\n');
				continue;
			}
			else if(o1 != x) 
			{
				l = L,r = R; L = 1,R = -1e9;
				if(st.size())
				{
					z = *st.begin();
					if(z <= r)//在区间内 
					{
						o2 = query(1,1,z);
						o3 = query(1,x+o2);
						if(o3 == x+o2) L1 = z+1,R1 = R;
					}
					z = *st.lower_bound(r+1);//st表还可以二分,太有实力了 
					if(z != n+100)//存在 
					{
						L = 1+z-r,R = z;
						if(query(1,L,R) == x) L1 = L,R1 = R;
					}
				}
			}
			if(L1 == 1e9) pc('n'),pc('o'),pc('n'),pc('e'),pc('\n');//无解 
			else print(L1),pc(' '),print(R1),pc('\n');
		}
		else 
		{
			read(x),read(y);
			if(a[x] == 1) st.erase(x);
			a[x] = y,change(1,x);
			if(a[x] == 1) st.insert(x);
		}
	} 
	flush();
	return 0;
}
/*
如果sum>=s的话,会二分出s,s+1两种可能 
s就行了,s+1就要么-2+1,要么-1 
出现s+1最后一个肯定是2,移动左端点,遇到1就赢了
左边没一或右边多一都会赢,win 
*/
posted @ 2025-04-24 19:58  kkxacj  阅读(18)  评论(0)    收藏  举报