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
*/
浙公网安备 33010602011771号