Cdq分治整体二分学习记录

时间:2023-03-08 16:46:13

这点东西前前后后拖了好几个星期才学会……还是自己太菜啊。

Cdq分治的思想是:把问题序列分割成左右两个,先单独处理左边,再处理左边对右边的影响,再单独处理右边。这样可以消去数据结构上的一个log,降低编码复杂度。

整体二分:当一个询问的答案满足二分性质时,我们可以按照答案的大小分割整个查询和修改序列。每次把序列分成互不相同的两部分。这样能把数据结构的二分拿出来,降低编码复杂度。

说白了,就是当你懒得写树套树或者惨遭卡内存时候的骗分办法。

好了,上例题吧:

BZOJ2683:

二维单点加,矩形查。可以kdtree或树套树水过。

然而这里要用cdq分治。我们把一个查询利用二维前缀和原理拆成4个,然后按x排序,对t(时间)cdq分治,用树状数组维护y上的前缀和。我们先处理t在左边对右边查询的影响,然后再递归处理左右。

代码:

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define debug cout
using namespace std;
const int maxn=8e5+1e2; int n; struct BinaryIndexTree {
int dat[maxn];
#define lowbit(x) (x&-x)
inline void update(int pos,int x) {
while( pos < maxn ) {
dat[pos] += x ,
pos += lowbit(pos);
}
}
inline int query(int pos) {
int ret = ;
while( pos )
ret += dat[pos] ,
pos -= lowbit(pos);
return ret;
}
}bit; struct QNode {
int tpe,x,y,aid,tme,lam;
friend bool operator < (const QNode &a,const QNode &b) {
if( a.x != b.x )
return a.x < b.x;
if( a.y != b.y )
return a.y < b.y;
return a.tpe < b.tpe;
}
}ns[maxn],tp[maxn]; int cnt,acn;
int ans[maxn]; inline void cdq(int l,int r) {
if( l == r )
return;
const int mid = ( l + r ) >> ;
for(int i=l;i<=r;i++) {
if( ns[i].tme <= mid && ns[i].tpe == )
bit.update(ns[i].y,ns[i].lam);
if( ns[i].tme > mid && ns[i].tpe == )
ans[ns[i].aid] += ns[i].lam * bit.query(ns[i].y);
}
for(int i=l;i<=r;i++)
if( ns[i].tme <= mid && ns[i].tpe == )
bit.update(ns[i].y,-ns[i].lam);
int l1 = l , l2 = mid + ;
for(int i=l;i<=r;i++)
if( ns[i].tme <= mid )
tp[l1++] = ns[i];
else
tp[l2++] = ns[i];
for(int i=l;i<=r;i++)
ns[i] = tp[i];
cdq(l,mid);
cdq(mid+,r);
} int main() {
static int t,x,y,xx,yy,lam;
scanf("%d",&n);
while( scanf("%d",&t) == && t != ) {
if( t == ) {
scanf("%d%d%d",&x,&y,&lam);
ns[++cnt].tpe = t , ns[cnt].x = x , ns[cnt].y = y ,
ns[cnt].lam = lam , ns[cnt].tme = cnt;
}
else {
scanf("%d%d%d%d",&x,&y,&xx,&yy);
--x , --y , ++acn;
ns[++cnt].tpe = t , ns[cnt].x = x , ns[cnt].y = y ,
ns[cnt].aid = acn , ns[cnt].tme = cnt , ns[cnt].lam = ;
ns[++cnt].tpe = t , ns[cnt].x = xx , ns[cnt].y = yy ,
ns[cnt].aid = acn , ns[cnt].tme = cnt , ns[cnt].lam = ;
ns[++cnt].tpe = t , ns[cnt].x = x , ns[cnt].y = yy ,
ns[cnt].aid = acn , ns[cnt].tme = cnt , ns[cnt].lam = -;
ns[++cnt].tpe = t , ns[cnt].x = xx , ns[cnt].y = y ,
ns[cnt].aid = acn , ns[cnt].tme = cnt , ns[cnt].lam = -;
}
}
sort(ns+,ns++cnt);
cdq(,cnt); for(int i=;i<=acn;i++)
printf("%d\n",ans[i]); return ;
}

BZOJ3110:

本来是一道树套树卡内存神题……

这题我们可以整体二分,对整个序列维护一个支持区间加,区间求和的树状数组(什么你不会?线段树也行),然后对于每个询问,如果>mid的区间和比查询的k小,则把询问分到左边,同时让k减去>mid的权值数量的区间和。反之直接分配到右边。

代码:

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lli long long int
#define debug cout
using namespace std;
const int maxn=5e4+1e2; struct BinaryIndexTree {
lli org[maxn],del[maxn];
int siz;
#define lowbit(x) (x&-x) inline void cupd(lli* dst,int pos,int add) {
while( pos <= siz )
dst[pos] += add , pos += lowbit(pos);
}
inline lli cqry(lli* sou,int pos) {
lli ret = ;
while( pos )
ret += sou[pos] , pos -= lowbit(pos);
return ret;
}
inline lli qryp(int pos) {
return cqry(org,pos) + cqry(del,pos) * pos;
}
inline void update(int l,int r,int add) {
cupd(org,l,-(l-)*add);
cupd(org,r,r*add);
cupd(del,l,add);
cupd(del,r,-add);
}
inline lli query(int l,int r) {
return qryp(r) - qryp(l-);
}
inline void resize(int ss) {
siz = ss;
}
}bit; int ans[maxn]; struct QNode { // type == 1 means update , type == 2 means query;
int tpe,l,r,x,qid;
lli k;
}ns[maxn],tpl[maxn],tpr[maxn]; inline void solve(int ql,int qr,int al,int ar) {
if( al == ar ) {
for(int i=ql;i<=qr;i++)
if( ns[i].tpe == ) {
ans[ns[i].qid] = al;
}
return;
}
const int amid = ( al + ar ) >> ;
int cntl = , cntr = ;
for(int i=ql;i<=qr;i++) {
if( ns[i].tpe == ) {
if( ns[i].x <= amid ) {
tpl[++cntl] = ns[i];
} else {
bit.update(ns[i].l,ns[i].r,);
tpr[++cntr] = ns[i];
}
}
else {
lli lam = bit.query(ns[i].l,ns[i].r);
if( ns[i].k <= lam ) {
tpr[++cntr] = ns[i];
} else {
ns[i].k -= lam;
tpl[++cntl] = ns[i];
}
}
}
for(int i=;i<=cntr;i++)
if( tpr[i].tpe == )
bit.update(tpr[i].l,tpr[i].r,-); const int qmid = ql + cntl - ;
int c1 = ql , c2 = qmid + ;
for(int i=;i<=cntl;i++)
ns[c1++] = tpl[i];
for(int i=;i<=cntr;i++)
ns[c2++] = tpr[i]; solve(ql,qmid,al,amid);
solve(qmid+,qr,amid+,ar);
} inline int getint() {
int ret = , ch ;
while( !isdigit(ch=getchar()) );
do ret=ret*+ch-''; while( isdigit(ch=getchar()) );
return ret;
}
signed main() {
static int n,m,qid;
n = getint() , m = getint();
bit.resize(n);
for(int i=;i<=m;i++) {
ns[i].tpe = getint() , ns[i].l = getint() , ns[i].r = getint();
if( ns[i].tpe == ) {
scanf("%d",&ns[i].x);
} else {
scanf("%lld",&ns[i].k);
ns[i].qid = ++qid;
}
} solve(,m,-n,n); for(int i=;i<=qid;i++)
printf("%d\n",ans[i]); return ;
}

BZOJ1901:

单点修改,查第k小的值。我们只需要把修改变成一个删除原权值的操作和一个插入新权值的操作,然后维护一个单点修改区间求和的树状数组就可以了。注意zoj上这题的双精题卡内存。

代码:

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#define debug cout
using namespace std;
const int maxn=3e4+1e2; struct BinaryIndexTree {
int dat[maxn],siz; #define lowbit(x) (x&-x) inline void update(int pos,int x) {
while( pos <= siz )
dat[pos] += x , pos += lowbit(pos);
}
inline int query(int pos) {
int ret = ;
while( pos )
ret += dat[pos] , pos -= lowbit(pos);
return ret;
}
inline void resize(int ss) {
siz = ss;
}
}bit; struct QNode {
int tpe; // tpe == 1 means query , 0 means change
int pos,val,del;
int st,ed,k,qid;
}ns[maxn],tp[][maxn]; int in[maxn],srt[maxn],ans[maxn],len; inline void solve(int ql,int qr,int al,int ar) {
if( al == ar ) {
for(int i=ql;i<=qr;i++)
if( ns[i].tpe )
ans[ns[i].qid] = al;
return;
}
const int amid = ( al + ar ) >> ;
int cntl = , cntr = ;
for(int i=ql;i<=qr;i++) {
if( ns[i].tpe ) {
const int kk = bit.query(ns[i].ed) - bit.query(ns[i].st-);
if( ns[i].k > kk ) {
ns[i].k -= kk ,
tp[][++cntr] = ns[i];
} else tp[][++cntl] = ns[i];
} else {
if( ns[i].val <= amid ) {
bit.update(ns[i].pos,ns[i].del);
tp[][++cntl] = ns[i];
} else tp[][++cntr] = ns[i];
}
}
for(int i=ql;i<=qr;i++)
if( !ns[i].tpe && ns[i].val <= amid )
bit.update(ns[i].pos,-ns[i].del);
const int qmid = ql + cntl - ;
memcpy(ns+ql,tp[]+,sizeof(tp[][])*cntl);
memcpy(ns+qmid+,tp[]+,sizeof(tp[][])*cntr);
solve(ql,qmid,al,amid);
solve(qmid+,qr,amid+,ar);
} inline int getint() {
int ret = , ch;
while( !isdigit(ch=getchar()) );
do ret=ret*+ch-''; while( isdigit(ch=getchar()) );
return ret;
} int main() {
static int n,m,cnt,qcnt;
static char com[];
n = getint() , m = getint();
for(int i=;i<=n;i++) {
ns[++cnt].val = in[i] = srt[++len] = getint() ,
ns[cnt].pos = i;ns[cnt].del = ;
}
for(int i=,p;i<=m;i++) {
scanf("%s",com);
if( *com == 'Q' ) {
ns[++cnt].tpe = , ns[cnt].qid = ++qcnt;
ns[cnt].st = getint() , ns[cnt].ed = getint() , ns[cnt].k = getint();
} else {
p = getint();
ns[++cnt].pos = p , ns[cnt].val = in[p] , ns[cnt].del = -;
ns[++cnt].pos = p , srt[++len] = ns[cnt].val = in[p] = getint() , ns[cnt].del = ;
}
}
sort(srt+,srt++len) , len = unique(srt+,srt++len) - srt - ;
for(int i=;i<=cnt;i++)
if( !ns[i].tpe ) {
ns[i].val = lower_bound(srt+,srt++len,ns[i].val) - srt;
} bit.resize(n);
solve(,cnt,,len); for(int i=;i<=qcnt;i++)
printf("%d\n",srt[ans[i]]); return ;
}

BZOJ1146:

这题可以权值线段树套树链剖分的线段树,也可以带修主席树。总之就是卡内存+不是人写的……

所以我们要整体二分,同上一题,只不过变到了树上。所以我们需要树链剖分线段树。

代码:

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#define debug cerr
using namespace std;
const int maxn=8e4+1e2; struct QNode {
int tpe; // 1 means query , 0 means change
int k,a,b,qid;
int pos,val,add;
}ns[maxn<<],tp[][maxn<<]; int in[maxn],srt[maxn<<],ans[maxn],len;
int s[maxn],t[maxn<<],nxt[maxn<<];
int fa[maxn],dep[maxn],siz[maxn],son[maxn],top[maxn],num[maxn],cov[maxn];
int l[maxn<<],r[maxn<<],lson[maxn<<],rson[maxn<<],su[maxn<<],cnt;
int n,m; inline void build(int pos,int ll,int rr) {
l[pos] = ll , r[pos] = rr;
if( ll == rr ) return;
const int mid = ( ll + rr ) >> ;
build(lson[pos]=++cnt,ll,mid);
build(rson[pos]=++cnt,mid+,rr);
}
inline void update(int pos,int tar,int x) {
if( tar < l[pos] || r[pos] < tar ) return;
if( l[pos] == r[pos] && l[pos] == tar ) {
su[pos] += x;
return;
}
update(lson[pos],tar,x);
update(rson[pos],tar,x);
su[pos] = su[lson[pos]] + su[rson[pos]];
}
inline int query(int pos,int ll,int rr) {
if( rr < l[pos] || r[pos] < ll ) return ;
if( ll <= l[pos] && r[pos] <= rr ) return su[pos];
return query(lson[pos],ll,rr) + query(rson[pos],ll,rr);
} inline void addedge(int from,int to) {
static int cnt = ;
t[++cnt] = to , nxt[cnt] = s[from] , s[from] = cnt;
}
inline void pre(int pos) {
siz[pos] = ;
for(int at=s[pos];at;at=nxt[at])
if( t[at] != fa[pos] ) {
fa[t[at]] = pos , dep[t[at]] = dep[pos] + ;
pre(t[at]);
siz[pos] += siz[t[at]];
son[pos] = siz[t[at]] > siz[son[pos]] ? t[at] : son[pos];
}
}
inline void dfs(int pos) {
top[pos] = pos == son[fa[pos]] ? top[fa[pos]] : pos;
num[pos] = pos == son[fa[pos]] ? num[fa[pos]] + : ;
for(int at=s[pos];at;at=nxt[at])
if( t[at] != fa[pos] )
dfs(t[at]);
if( !son[pos] )
build(cov[top[pos]]=++cnt,num[top[pos]],num[pos]);
}
inline int chain(int a,int b) {
int ret = ;
while( top[a] != top[b] ) {
if( dep[top[a]] > dep[top[b]] ) {
ret += query(cov[top[a]],num[top[a]],num[a]),
a = fa[top[a]];
} else {
ret += query(cov[top[b]],num[top[b]],num[b]),
b = fa[top[b]];
}
}
ret += query(cov[top[a]],min(num[a],num[b]),max(num[a],num[b]));
return ret;
} inline void solve(int ql,int qr,int al,int ar) {
if( al == ar ) {
for(int i=ql;i<=qr;i++)
if( ns[i].tpe )
ans[ns[i].qid] = al;
return;
}
const int amid = ( al + ar ) >> ;
int cntl = , cntr = ;
for(int i=ql;i<=qr;i++) {
if( ns[i].tpe ) {
int kk = chain(ns[i].a,ns[i].b);
if( ns[i].k > kk ) {
ns[i].k -= kk , tp[][++cntl] = ns[i];
} else tp[][++cntr] = ns[i];
} else {
if( ns[i].val > amid ) {
const int &pos = ns[i].pos;
update(cov[top[pos]],num[pos],ns[i].add);
tp[][++cntr] = ns[i];
} else tp[][++cntl] = ns[i];
}
}
for(int i=qr;i>=ql;i--)
if( ns[i].val > amid ) {
const int &pos = ns[i].pos;
update(cov[top[pos]],num[pos],-ns[i].add);
}
const int qmid = ql + cntl - ; memcpy( ns + ql , tp[] + , sizeof(tp[][]) * cntl );
memcpy( ns + qmid + , tp[] + , sizeof(tp[][]) * cntr ); solve(ql,qmid,al,amid);
solve(qmid+,qr,amid+,ar);
} inline int getint() {
int ret = , ch;
while( !isdigit(ch=getchar()) );
do ret=ret*+ch-''; while( isdigit(ch=getchar()) );
return ret;
}
int main() {
static int qcnt,qlen;
n = getint() , m = getint();
qlen = n;
for(int i=;i<=n;i++)
ns[i].pos = i , in[i] = ns[i].val = srt[++len] = getint() , ns[i].add = ;
for(int i=,a,b;i<n;i++) {
a = getint() , b = getint();
addedge(a,b) , addedge(b,a);
}
for(int i=,k,p;i<=m;i++) {
k = getint();
if( !k ) {
p = getint();
++qlen , ns[qlen].pos = p , ns[qlen].val = in[p] , ns[qlen].add = -;
++qlen , ns[qlen].pos = p , in[p] = ns[qlen].val = srt[++len] = getint() , ns[qlen].add = ;
} else {
++qlen , ns[qlen].tpe = , ns[qlen].a = getint() , ns[qlen].b = getint() , ns[qlen].k = k , ns[qlen].qid = ++qcnt;
}
} sort(srt+,srt++len);
len = unique(srt+,srt++len) - srt - ;
for(int i=;i<=qlen;i++)
if( ! ns[i].tpe ) ns[i].val = lower_bound(srt+,srt++len,ns[i].val) - srt; pre();dfs();
solve(,qlen,,len); for(int i=;i<=qcnt;i++)
if( ans[i] ) printf("%d\n",srt[ans[i]]);
else puts("invalid request!"); return ;
}

BZOJ3237:

删除一组边问你是否联通。我们可以cdq分治求解。

维护每条边被删除的次数,递归左右时,如果这条边在某一侧被删除次数为0了则合并两边点所在的并查集。这里不要路径压缩,我们需要按秩合并。我们可以记录一个栈来回滚操作(当然你非得用主席树可持久化那也行)。查询时就看一下点1所在的联通块大小是否为n即可。是不是很好写啊?

代码:

 #include<cstdio>
using namespace std;
const int maxn=1e5+1e2,maxm=2e5+1e2,maxk=1e5+1e2; int fa[maxn],siz[maxn];
int a[maxm],b[maxm];
int len[maxk],met[maxk][];
int cnt[maxm],stk[maxn],top;
char ans[maxk];
int n; inline int findfa(int x) {
return fa[x] == x ? x : findfa(fa[x]);
} inline void merge(int l,int r) {
for(int i=l;i<=r;i++)
for(int j=;j<=len[i];j++) {
const int k = met[i][j];
if( --cnt[k] ) continue;
int aa = findfa(a[k]) , bb = findfa(b[k]);
if( aa == bb ) continue;
if( siz[aa] > siz[bb] ) fa[bb] = aa , siz[aa] += siz[bb] , stk[++top] = bb;
else fa[aa] = bb , siz[bb] += siz[aa] , stk[++top] = aa;
}
}
inline void resetcnt(int l,int r) {
for(int i=l;i<=r;i++)
for(int j=;j<=len[i];j++)
++cnt[met[i][j]];
}
inline void resetfa(int tar) {
while( top > tar ) {
const int pos = stk[top--];
siz[fa[pos]] -= siz[pos] , fa[pos] = pos;
}
} inline void solve(int ll,int rr) {
if( ll == rr ) {
ans[ll] = ( siz[findfa()] == n );
return;
}
const int mid = ( ll + rr ) >> , mem = top; merge(ll,mid);
solve(mid+,rr);
resetfa(mem);
resetcnt(ll,mid); merge(mid+,rr);
solve(ll,mid);
resetfa(mem);
resetcnt(mid+,rr);
} inline void init() {
for(int i=;i<=n;i++)
fa[i] = i , siz[i] = ;
} int main() {
static int m,k;
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++)
scanf("%d%d",a+i,b+i);
scanf("%d",&k);
for(int i=;i<=k;i++) {
scanf("%d",len+i);
for(int j=;j<=len[i];j++)
scanf("%d",met[i]+j);
} init();
resetcnt(,k);
for(int i=;i<=m;i++)
if( !cnt[i] ) {
int aa = findfa(a[i]) , bb = findfa(b[i]);
if( aa == bb ) continue;
if( siz[aa] > siz[bb] ) fa[bb] = aa , siz[aa] += siz[bb];
else fa[aa] = bb , siz[bb] += siz[aa];
} solve(,k); for(int i=;i<=k;i++)
puts( ans[i] ? "Connected" : "Disconnected" ); return ;
}

BZOJ2001:

动态维护最小生成树。

我们可以LCT,可做但据说并不能AC(常数巨大)。

所以我们需要cdq分治。对于每层,我们删除无用边,缩点必需边,可以把边数化成O(n)级别的,然后再向下递归即可。底层直接更新权值,计算最小生成树,记录答案。

实现的时候,我们记录每一条边实际权值,和它原来的编号和在当前边序列中的编号的映射关系即可。每层并查集初始化的时候仅初始化在这层存在的边。

代码:

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#define lli long long int
using namespace std;
const int maxn=2e4+1e2,maxm=5e4+1e2,maxd=;
const int inf=0x3f3f3f3f; struct Edge {
int a,b,val,id;
friend bool operator < (const Edge &a,const Edge &b) {
return a.val < b.val;
}
}ess[maxd][maxm],now[maxm],etp[maxm];
int opeid[maxm],opeval[maxm];
int wei[maxm],cov[maxm]; // weight of edge , convert real id to operated id.
int fa[maxn];
lli ans[maxm]; inline int findfa(int x) {
return fa[x] == x ? x : fa[x] = findfa(fa[x]);
}
inline void merge(int x,int y) {
x = findfa(x) , y = findfa(y);
if( x == y ) return;
fa[x] = y;
}
inline void reset(Edge* es,int len) {
for(int i=;i<=len;i++)
fa[es[i].a] = es[i].a ,
fa[es[i].b] = es[i].b;
} inline lli contraction(Edge* es,int &len) {
lli ret = ;
int tpl = ; reset(es,len);
sort(es+,es++len);
for(int i=;i<=len;i++)
if( findfa(es[i].a) != findfa(es[i].b) ) { // find a mst
etp[++tpl] = es[i] , merge(es[i].a,es[i].b);
} reset(etp,tpl);
for(int i=;i<=tpl;i++)
if( etp[i].val != -inf ) {
ret += etp[i].val , merge(etp[i].a,etp[i].b);
} tpl = ;
for(int i=;i<=len;i++)
if( findfa(es[i].a) != findfa(es[i].b) ) {
etp[++tpl] = es[i];
etp[tpl].a = findfa(es[i].a) , etp[tpl].b = findfa(es[i].b) ,
cov[es[i].id] = tpl;
} reset(es,len);
len = tpl;
memcpy(es+,etp+,sizeof(es[])*len); return ret;
}
inline void reduction(Edge* es,int &len) {
int tpl = ; reset(es,len);
sort(es+,es++len);
for(int i=;i<=len;i++)
if( findfa(es[i].a) != findfa(es[i].b) ) {
merge(es[i].a,es[i].b);
etp[++tpl] = es[i];
cov[es[i].id] = tpl;
}
else if( es[i].val == inf ) {
etp[++tpl] = es[i];
cov[es[i].id] = tpl;
} reset(es,len) , len = tpl;
memcpy(es+,etp+,sizeof(es[])*len);
}
inline lli mst(Edge* es,int len) {
lli ret = ;
reset(es,len);
sort(es+,es++len);
for(int i=;i<=len;i++)
if( findfa(es[i].a) != findfa(es[i].b) )
ret += es[i].val , merge(es[i].a,es[i].b);
return ret;
} inline void solve(Edge* es,int dep,int len,int ll,int rr,lli res) {
if( ll == rr )
wei[opeid[ll]] = opeval[ll];
for(int i=;i<=len;i++) {
es[i].val = wei[es[i].id];
now[i] = es[i] , cov[now[i].id] = i;
}
if( ll == rr ) {
ans[ll] = res + mst(now,len);
return;
} for(int i=ll;i<=rr;i++) {
now[cov[opeid[i]]].val = -inf;
}
res += contraction(now,len); for(int i=ll;i<=rr;i++)
now[cov[opeid[i]]].val = inf;
reduction(now,len); memcpy(ess[dep+]+,now+,sizeof(now[])*len); const int mid = ( ll + rr ) >> ;
solve(ess[dep+],dep+,len,ll,mid,res);
solve(ess[dep+],dep+,len,mid+,rr,res);
} inline char nextchar() {
static char buf[<<],*st=buf+(<<),*ed=buf+(<<);
if( st == ed ) ed = buf + fread(st=buf,,<<,stdin);
return st != ed ? *st++ : -;
}
inline int getint() {
int ret = ,ch;
while( !isdigit(ch=nextchar()) );
do ret=ret*+ch-''; while( isdigit(ch=nextchar()) );
return ret;
}
int main() {
static int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=,a,b,l;i<=m;i++) {
scanf("%d%d%d",&a,&b,&l);
ess[][i] = (Edge){a,b,l,i};
wei[i] = l;
}
for(int i=;i<=q;i++)
scanf("%d%d",opeid+i,opeval+i); solve(ess[],,m,,q,); for(int i=;i<=q;i++)
printf("%lld\n",ans[i]); return ;
}

为什么代码都写得这么长这么丑还常数那么大?……果然还是自己太菜啊。