1 条题解

  • 0
    @ 2025-8-24 23:03:42

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Register_int
    分道扬镳

    搬运于2025-08-24 23:03:42,当前版本为作者最后更新于2024-09-10 20:08:42,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    异或就是个很烦的东西,所以第一步是把异或处理掉。来拆一下条件:

    • 递增且前缀异或和递增。说明异或的过程中最高位只能不断增加,所以最高位的位置严格递增。
    • 后缀异或和递增。说明异或时不会有任何一个数的最高位被异或为 00

    于是可以得到等价充要条件:

    一个数列是好的,当且仅当这些数的最高位所在的位单调递增,且这些位上均存在奇数个 11

    这个条件的形式已经足够简洁,可以支持我们对此进行计数。下文 mm+1m\gets m+1 将限制变为 <m<m

    显然对于任意正整数 nn,有 $\sum_{2\mid i}\binom ni=\sum_{2\nmid i}\binom ni=2^{n-1}$。相信这个大家都会证明。假设我们已经将所有最高位填完了,设 ai=2bia_i=2^{b_i},那么再往里面填 11 的方案数是多少呢?可以发现:

    若这一层有某个数的最高位,则这一行只能再偶数个。 否则,这一行可以随便填。

    容易发现,当我们拆位后,每一位上填的方案数是好算的。设填之前 00 的个数为 c0c_011 的个数为 c1c_1,则方案数是 2c0c12^{c_0-c_1}。全部乘起来,特判下最后一个数的最高位,得到答案为 2bin+12^{\sum b_i-n+1}
    考虑单次询问怎么做。将 mm 拆成若干个值域段。设 m=2k1+2k2++2ktm=2^{k_1}+2^{k_2}+\cdots+2^{k_t},其中 k1>k2>>kt0k_1>k_2>\cdots>k_t\ge0,则拆成:

    $$[0,2^{k_1}),[2^{k_1},2^{k_1}+2^{k_2}),\cdots,[m-2^{k_t},m) $$

    这样 mm 的低位都在 [0,2k)[0,2^k) 的范围内滑动。继续看倒数第二个数的最高位能在哪。假设当前枚举的值域段到了 mm 从高往低的第 ii11

    • 最高位可以是 mm 已经被固定的 00
    • 最高位可以在 aia_i,然后最后一个数的这一位不能是 11

    第二类情况恰好扣除一半,直接按照前面的公式计算。问题变成快速计算下面这个东西:

    定义整数序列 aa 的权值为 2ai2^{\sum a_i},求所有递增且值域为 [1,m][1,m] 的序列 aa 的权值和。

    显然能暴力 dp,时间复杂度 O(n2)O(n^2),可以通过前 22 个子任务,搭配数据结构维护可以通过前 33 个子任务。

    继续优化。严格递增是坏的,所以考虑 ai=aiia'_i=a_i-i 变成非严格递增,就能转化为格路计数问题。将 aa'n×(mn)n\times(m-n) 的网格上的格路建立映射:在第 ii 格前先向上走到 aia'_i 的位置。问题变为所有从左下到右上的路径右下部分面积的 22 次幂之和。

    对面积拆贡献。若在高度为 hh 的位置有一条向右走的边,那么这条边会对面积贡献 hh,而 hh 的值其实就是这条边之前向上的边数。考虑把格路转化为 0/10/1 串将向上的边标为 11,向右的边标为 00,则面积为该序列的逆序对个数。

    可重串的逆序对是难以刻画的,但是我们可以用排列的逆序对来刻画 0/10/1 串的逆序对。具体地,假设长度为 KK 的排列权值为 f(K)f(K),那么对于一个长度为 N+MN+M0,10,1 数量各为 N,MN,M0/10/1 串,其权值可以通过“将其视作排列的权值”除去 0,10,1 内部的贡献来计算,也即 f(N+M)f(N)f(M)\frac{f(N+M)}{f(N)f(M)}

    对于长度为 KK 的排列,我们可以考虑递推。我们在序列末尾插入数时,可以不考虑在排列的某个位置插入,而是在值域上插入一个数。那么插入 0,1,,K0,1,\cdots,K 会产生的逆序对数就与之前的排列无关了,为 K,K1,,0K,K-1,\cdots,0。所以造成的贡献为 2K+2K1++20=2K+112^K+2^{K-1}+\cdots+2^0=2^{K+1}-1,即 f(K+1)=(2K+11)f(K)f(K+1)=(2^{K+1}-1)f(K)

    化简至此容易做到 O(n)O(1)O(n)-O(1),可以通过前 44 个子任务,实现精细可以通过前 55 个子任务。

    接下来考虑如何处理多组询问。将 mm 划分成若干个 0/10/1 段,每次操作都只会造成 O(1)O(1) 个段的改动。然后对于连续 00 段和 11 段,都可以计算其贡献,00 对其后的 11 贡献,11 对前面的 00 贡献。考虑拿两个平衡树,分别维护 0011 的连续段。段内求和都是好算的,段外只要实现前、后缀和即可。

    总的时间复杂度是 O(n+qlogq)O(n+q\log q),常数取决于你怎么维护。可以通过所有子任务。想清楚的话实现还是较为简单的。

    当然也可以用线段树维护,不过笔者并没有考虑到线段树做法。如果空间被卡的出题人在这谢罪了。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define dIO_USE_BUFFER
    struct IO{
    #ifdef dIO_USE_BUFFER
    const static int BUFSIZE=1<<20;char ibuf[BUFSIZE],obuf[BUFSIZE],*p1,*p2,*pp;inline int getchar(){return(p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,BUFSIZE,stdin),p1==p2)?EOF:*p1++);}inline int putchar(char x){return((pp-obuf==BUFSIZE&&(fwrite(obuf,1,BUFSIZE,stdout),pp=obuf)),*pp=x,pp++),x;}inline IO&flush(){return fwrite(obuf,1,pp-obuf,stdout),pp=obuf,fflush(stdout),*this;}IO(){p1=p2=ibuf,pp=obuf;}~IO(){flush();}
    #else
    int(*getchar)()=&::getchar;int(*putchar)(int)=&::putchar;inline IO&flush(){return fflush(stdout),*this;}
    #endif
    string _sep=" ";int k=2;template<typename Tp,typename enable_if<is_integral<Tp>::value||is_same<Tp,__int128_t>::value>::type* =nullptr>inline int read(Tp&s){int f=1,ch=getchar();s=0;while(!isdigit(ch)&&ch!=EOF)f=(ch=='-'?-1:1),ch=getchar();if(ch==EOF)return false;while(ch=='0')ch=getchar();while(isdigit(ch))s=s*10+(ch^48),ch=getchar();s*=f;return true;}template<typename Tp,typename enable_if<is_floating_point<Tp>::value>::type* =nullptr>inline int read(Tp&s){int f=1,ch=getchar();s=0;while(!isdigit(ch)&&ch!='.'&&ch!=EOF)f=(ch=='-'?-1:1),ch=getchar();if(ch==EOF)return false;while(isdigit(ch))s=s*10+(ch^48),ch=getchar();if(ch=='.'){Tp eps=0.1;ch=getchar();while(isdigit(ch))s=s+(ch^48)*eps,ch=getchar(),eps/=10;}s*=f;return true;}inline int read(char&ch){ch=getchar();while(isspace(ch)&&ch!=EOF)ch=getchar();return ch!=EOF;}inline int read(char*c){char ch=getchar(),*s=c;while(isspace(ch)&&ch!=EOF)ch=getchar();while(!isspace(ch)&&ch!=EOF)*(c++)=ch,ch=getchar();*c='\0';return s!=c;}inline int read(string&s){s.clear();char ch=getchar();while(isspace(ch)&&ch!=EOF)ch=getchar();while(!isspace(ch)&&ch!=EOF)s+=ch,ch=getchar();return s.size()>0;}template<typename Tp=int>inline Tp read(){Tp x;read(x);return x;}template<typename Tp,typename...Ts>inline int read(Tp&x,Ts&...val){return read(x)&&read(val...);}inline int getline(char*c,const char&ed='\n'){char ch=getchar(),*s=c;while(ch!=ed&&ch!=EOF)*(c++)=ch,ch=getchar();*c='\0';return s!=c;}inline int getline(string&s,const char&ed='\n'){s.clear();char ch=getchar();while(ch!=ed&&ch!=EOF)s+=ch,ch=getchar();return s.size()>0;}template<typename Tp,typename enable_if<is_integral<Tp>::value||is_same<Tp,__int128_t>::value>::type* =nullptr>inline IO&write(Tp x){if(x<0)putchar('-'),x=-x;static char sta[41];int top=0;do sta[top++]=x%10^48,x/=10;while(x);while(top)putchar(sta[--top]);return*this;}inline IO&write(const string&str){for(char ch:str)putchar(ch);return*this;}inline IO&write(const char*str){while(*str!='\0')putchar(*(str++));return*this;}inline IO&write(char*str){return write((const char*)str);}inline IO&write(const char&ch){return putchar(ch),*this;}template<typename Tp,typename enable_if<is_floating_point<Tp>::value>::type* =nullptr>inline IO&write(Tp x){if(x>1e18||x<-1e18){write("[Floating point overflow]");throw;}if(x<0)putchar('-'),x=-x;const static long long pow10[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000,100000000000,1000000000000,10000000000000,100000000000000,1000000000000000,10000000000000000,100000000000000000,100000000000000000,100000000000000000};const auto&n=pow10[k];long long whole=x;double tmp=(x-whole)*n;long long frac=tmp;double diff=tmp-frac;if(diff>0.5){++frac;if(frac>=n)frac=0,++whole;}else if(diff==0.5&&((frac==0U)||(frac&1U)))++frac;write(whole);if(k==0U){diff=x-whole;if((!(diff<0.5)||(diff>0.5))&&(whole&1))++whole;}else{putchar('.');static char sta[21];int count=k,top=0;while(frac){sta[top++]=frac%10^48;frac/=10,count--;}while(count--)putchar('0');while(top)putchar(sta[--top]);}return*this;}template<typename Tp,typename...Ts>inline IO&write(Tp x,Ts...val){return write(x),write(_sep),write(val...),*this;}template<typename...Ts>inline IO&writeln(Ts...val){return write(val...),putchar('\n'),*this;}template<typename...Ts>inline IO&writesp(Ts...val){return write(val...),putchar(' '),*this;}inline IO&writeln(void){return putchar('\n'),*this;}inline IO&sep(const string&s=" "){return _sep=s,*this;}inline IO&prec(const int&K=2){return k=K,*this;}}io;
    
    typedef long long ll;
    typedef unsigned uint;
    
    const int MAXN = 1e6 + 10;
    const int MAXM = 1e7 + 30;
    const int mod = 998244353;
    
    inline int add(int x, int y) { return x += y, x < mod ? x : x - mod; }
    inline int del(int x, int y) { return x -= y, x < 0 ? x + mod : x; }
    
    inline 
    int qpow(int b, int p) {
    	int res = 1;
    	for (; p; p >>= 1, b = (ll)b * b % mod) if (p & 1) res = (ll)res * b % mod;
    	return res;
    }
    
    int p[MAXM], p2[MAXM], pp2[MAXM];
    
    int q_fac[MAXM], q_ifac[MAXM];
    
    inline 
    void init(int n) {
    	for (int i = 1; i <= n; i++) p[i] = add(p[i - 1], p[i - 1] + 1);
    	*p2 = 1;
    	for (int i = 1; i <= n; i++) p2[i] = add(p2[i - 1], p2[i - 1]);
    	*pp2 = 1;
    	for (int i = 1; i <= n; i++) pp2[i] = (ll)pp2[i - 1] * p2[i - 1] % mod;
    	*q_fac = 1;
    	for (int i = 1; i < n; i++) q_fac[i] = (ll)q_fac[i - 1] * p[i] % mod;
    	q_ifac[n - 1] = qpow(q_fac[n - 1], mod - 2);
    	for (int i = n - 1; i; i--) q_ifac[i - 1] = (ll)q_ifac[i] * p[i] % mod;
    }
    
    inline 
    int q_binom(int n, int m) {
    	if (n < 0 || m < 0 || n < m) return 0;
    	return (ll)q_fac[n] * q_ifac[m] % mod * q_ifac[n - m] % mod;
    }
    
    inline 
    int f(int n, int m) {
    	return (ll)q_binom(n, m) * pp2[m - 1] % mod;
    }
    
    mt19937 eng(time(0));
    
    struct FHQ_treap {
    	
    	struct node {
    		int ls, rs, size; uint w;
    		int l, r; int val, sum;
    		node(int l = 0, int r = 0, int val = 0) : 
    			ls(0), rs(0), size(1), w(eng()), 
    			l(l), r(r), val(val), sum(val) {} 
    	} t[MAXN]; int cnt = 0, rt = 0;
    	
    	node operator [] (int k) const { return t[k]; }
    	
    	inline 
    	int create(int l, int r, int val) {
    		return t[++cnt] = node(l, r, val), cnt;
    	}
    	
    	inline 
    	void pushup(int p) {
    		t[p].size = t[t[p].ls].size + t[t[p].rs].size + 1;
    		t[p].sum = add(add(t[t[p].ls].sum, t[t[p].rs].sum), t[p].val);
    	}
    	
    	void split(int p, int k, int &x, int &y) {
    		if (!p) return x = y = 0, void();
    		if (t[p].r <= k) x = p, split(t[p].rs, k, t[p].rs, y);
    		else y = p, split(t[p].ls, k, x, t[p].ls); pushup(p);
    	}
    	
    	int merge(int x, int y) {
    		if (!x || !y) return x | y;
    		if (t[x].w < t[y].w) return t[x].rs = merge(t[x].rs, y), pushup(x), x;
    		return t[y].ls = merge(x, t[y].ls), pushup(y), y;
    	}
    	
    	inline 
    	void insert(int l, int r, int val) {
    		int ls, rs; split(rt, r, ls, rs);
    		rt = merge(merge(ls, create(l, r, val)), rs);
    	}
    	
    	inline 
    	void erase(int k) {
    		int ls, rs, p; split(rt, k, ls, rs), split(ls, k - 1, ls, p);
    		rt = merge(merge(ls, merge(t[p].ls, t[p].rs)), rs);
    	}
    	
    	inline 
    	int ask(int k, bool f) {
    		int ls, rs, ans; split(rt, k, ls, rs);
    		ans = f ? t[rs].sum : t[ls].sum, rt = merge(ls, rs);
    		return ans;
    	}
    	
    	inline 
    	int prev(int k) {
    		int ls, rs, p; split(rt, k - 1, ls, rs);
    		for (p = ls; t[p].rs; p = t[p].rs);
    		return rt = merge(ls, rs), p;
    	}
    	
    	inline 
    	int next(int k) {
    		int ls, rs, p; split(rt, k, ls, rs);
    		for (p = rs; t[p].ls; p = t[p].ls);
    		return rt = merge(ls, rs), p;
    	}
    	
    	inline 
    	int qmax() {
    		int p = rt;
    		for (; t[p].rs; p = t[p].rs);
    		return t[p].r;
    	}
    	
    };
    
    FHQ_treap t0, t1; int ans = 0, n;
    
    inline 
    int calc_0(int l, int r) {
    	return del(f(r + 1, n - 1), f(l, n - 1));
    }
    
    inline 
    int calc_1_x(int l, int r) {
    	return del(p2[r + 1], p2[l]);
    }
    
    inline 
    int calc_1_y(int l, int r) {
    	int res = (ll)p2[r] * f(r + 1, n - 1) % mod;
    	if (l) res = del(res, (ll)p2[l - 1] * f(l, n - 1) % mod);
    	return res;
    }
    
    inline 
    void insert_0(int l, int r) {
    	int x = calc_0(l, r); ans = add(ans, (ll)x * t1.ask(l - 1, 0) % mod);
    	int p = t0.prev(r), q = t0.next(r);
    	assert(!p || t0[p].r < l), assert(!q || t0[q].l > r);
    	if (p && t0[p].r == l - 1) l = t0[p].l, t0.erase(t0[p].r);
    	if (q && t0[q].l == r + 1) r = t0[q].r, t0.erase(t0[q].r);
    	t0.insert(l, r, calc_0(l, r));
    }
    
    inline 
    void erase_0(int l, int r) {
    	int x = calc_0(l, r); ans = del(ans, (ll)x * t1.ask(l - 1, 0) % mod);
    	int k = t0.next(r - 1);
    	int pl = t0[k].l, pr = t0[k].r; t0.erase(pr);
    	if (l > pl) t0.insert(pl, l - 1, calc_0(pl, l - 1));
    	if (r < pr) t0.insert(r + 1, pr, calc_0(r + 1, pr));
    }
    
    inline 
    void insert_1(int l, int r) {
    	int x = calc_1_x(l, r), y = calc_1_y(l, r);
    	ans = add(ans, add((ll)x * t0.ask(r, 1) % mod, y));
    	int p = t1.prev(r), q = t1.next(r);
    	assert(!p || t1[p].r < l), assert(!q || t1[q].l > r);
    	if (p && t1[p].r == l - 1) l = t1[p].l, t1.erase(t1[p].r);
    	if (q && t1[q].l == r + 1) r = t1[q].r, t1.erase(t1[q].r);
    	t1.insert(l, r, calc_1_x(l, r));
    }
    
    inline 
    void erase_1(int l, int r) {
    	int x = calc_1_x(l, r), y = calc_1_y(l, r);
    	ans = del(ans, add((ll)x * t0.ask(r, 1) % mod, y));
    	int k = t1.next(r - 1);
    	int pl = t1[k].l, pr = t1[k].r; t1.erase(pr);
    	if (l > pl) t1.insert(pl, l - 1, calc_1_x(pl, l - 1));
    	if (r < pr) t1.insert(r + 1, pr, calc_1_x(r + 1, pr));
    }
    
    inline 
    void add(int x) {
    	int k0 = t0.next(x - 1), k1 = t1.next(x - 1);
    	int l0 = t0[k0].l, r0 = t0[k0].r, l1 = t1[k1].l, r1 = t1[k1].r;
    	if (k0 && l0 <= x && x <= r0) erase_0(x, x), insert_1(x, x);
    	else if (k1 && l1 <= x && x <= r1) {
    		erase_1(x, r1), insert_0(x, r1);
    		if (k0) erase_0(r1 + 1, r1 + 1); insert_1(r1 + 1, r1 + 1);
    	} else {
    		k1 = t1.prev(x), r1 = t1[k1].r;
    		insert_1(x, x); if (r1 < x - 1) insert_0(r1 + 1, x - 1);
    	}
    }
    
    inline 
    void del(int x) {
    	int k0 = t0.next(x - 1), k1 = t1.next(x - 1);
    	int l0 = t0[k0].l, r0 = t0[k0].r, l1 = t1[k1].l, r1 = t1[k1].r;
    	if (k1 && l1 <= x && x <= r1) {
    		erase_1(x, x), r1 = t1.qmax();
    		if (r1 < x - 1) erase_0(r1 + 1, x - 1); else insert_0(x, x); 
    	} else if (k0 && l0 <= x && x <= r0) {
    		erase_0(x, r0), insert_1(x, r0);
    		erase_1(r0 + 1, r0 + 1), r1 = t1.qmax();
    		if (r1 > r0) insert_0(r0 + 1, r0 + 1);
    	}
    }
    
    inline 
    int query() {
    	int r1 = t1.qmax();
    	return add(del(ans, calc_1_y(r1, r1)), f(r1, n));
    }
    
    int q;
    
    int main() {
    	io.read(n, q), init(1e7 + 30), insert_1(0, 0);
    	for (int x, opt; q--;) {
    		io.read(opt);
    		if (opt == 0) io.read(x), add(x);
    		else if (opt == 1) io.read(x), del(x);
    		else io.writeln(query());
    	}
    }
    

    其实 O(n2)O(n)O(n^2)\to O(n) 的优化是一个叫做 q-analog 的普及度还算高科技,但是主播在完全不知道这个的情况下研发出了本题做法(甚至是快讲评的时候才知道的),导致完全没料想到这个组合意义会被直接秒……如果影响到大家的比赛体验也在这里磕头了。

    • 1

    信息

    ID
    10017
    时间
    3000ms
    内存
    1024MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者