Codeforces 1511G - Chips on a Board(01trie/倍增)
一道名副其实的 hot tea
首先显然可以发现这俩人在玩 Nim 游戏,因此对于一个 \(c_i\in[l,r]\) 其 SG 值就是 \(c_i-l\),最终游戏的 SG 值就是 \(\oplus_{c_i\in[l,r]}(c_i-l)\),如果该值为 \(0\) 答案就是 B,否则答案是 A。
从这一步开始就有好几种不同复杂度的做法了,下面将一一介绍它们。
下视 \(n,m,q\) 同阶。
做法一:暴力
迫真 · \(2\times 10^5\) 给 \(n^2\) 暴力踩过去。
你看这 \(2\times 10^5\) 的数据范围,你不写 \(n^2\) 写啥呢,难道还傻傻地想 \(n\log^2n\) 或是 \(n\log n\) 的做法?
不要笑,现场 rk1 那位大哥就是暴力碾标算还抵过了 100 多发 hack。不得不说 CF 机子是真的快(
就没啥好说的了,暴力从第一个 \(\ge l\) 的位置开始枚举把它们异或起来即可。
时间复杂度 \(n^2\)。
做法二:莫队+01-trie
允许离线+静态区间问题,这无不启发我们往莫队的方向想。又因为此题涉及异或,因此考虑 01-trie,具体来说我们建立一棵 01-trie 维护当前莫队表示的区间中所有元素与 \(l\),那么向左/右移动右端点时 01-trie 时删除/插入元素,这个维护起来异常容易的不再赘述。向左/右移动左端点时需要先将 01-trie 中所有元素 \(+1/-1\) 再插入/删除新的元素,全局 \(\pm 1\) 这个操作的维护方法算是经典套路了,考虑从低到高将序列中的元素插入 01-trie,那么全局 \(+1\) 相当于从根节点开始 DFS,每次 DFS 时交换当前节点两个儿子再 DFS 左儿子,如此进行下去,每次交换都实时更新答案即可。
时间复杂度 \(n\sqrt{n}\log n\)
做法三:分块+01-trie
一个我自己 yy 出来的算法(
上面莫队的做法每次向右移动都要插入新的元素,复杂度就总要多一个 log,并且莫队移动复杂度本身就带个根号导致这个 log 不太好摊,因此考虑将莫队换成分块。我们考虑设一个阈值 \(B\) 然后每 \(B\) 个元素一块,在下文中方便起见,设 \(L_i,R_i\) 分别表示每一块的左右端点。那么对于每一块 \(i\),我们设 \(v_{i,j}\) 表示这一块中所有 \(c_k\ne 0\) 的元素 \(k\) 的 \(k-j\) 的异或和,其中 \(j\) 小于第 \(i\) 块的左端点,那么每次查询就整块调用预处理求得的值,散块暴力即可。那么怎么求 \(v_{i,j}\) 呢?我们考虑将这一块中所有 \(c_k\ne 0\) 的 \(k\) 的 \(k-L_i+1\) 插入 01-trie,然后从 \(L_i-1\) 开始往前遍历,每次进行全局 \(+1\) 操作,那么每次 \(+1\) 后得到的异或和就分别是我们要求的 \(v_{i,L_i-1},v_{i,L_i-2},\cdots\)。
显然取 \(B=\sqrt{n\log n}\) 时复杂度最优。
时间复杂度 \(n\sqrt{n\log n}\)
const int MAXN=2e5;
const int BLK=300;
const int MAXP=1e5;
const int LOG_N=20;
int n,m,qu,cnt[MAXN+5],v[BLK+5][MAXN+5];
int bel[MAXN+5],L[BLK+5],R[BLK+5];
int ch[MAXP+5][2],siz[MAXP+5],ncnt=0,rt=0,val[MAXP+5];
void clear(){
memset(ch,0,sizeof(ch));ncnt=rt=0;
memset(siz,0,sizeof(siz));memset(val,0,sizeof(val));
}
void pushup(int k,int d){val[k]=val[ch[k][0]]^val[ch[k][1]]^((siz[ch[k][1]]&1)?(1<<d):0);}
void insert(int &k,int v,int d){
if(!k) k=++ncnt;siz[k]++;if(d==LOG_N) return;
insert(ch[k][v>>d&1],v,d+1);pushup(k,d);
}
void add1(int k,int d){
if(!k) return;swap(ch[k][0],ch[k][1]);
add1(ch[k][0],d+1);pushup(k,d);
}
int query(int l,int r){
if(bel[l]==bel[r]){
int res=0;
for(int i=l;i<=r;i++) if(cnt[i]) res^=(i-l);
return res;
} else {
int res=0;
for(int i=l;i<=R[bel[l]];i++) if(cnt[i]) res^=(i-l);
for(int i=L[bel[r]];i<=r;i++) if(cnt[i]) res^=(i-l);
for(int i=bel[l]+1;i<bel[r];i++) res^=v[i][l];
return res;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++) scanf("%d",&x),cnt[x]^=1;
int blk_sz=max((int)sqrt(m*log2(m)),1),blk_cnt=(m-1)/blk_sz+1;
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,m);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
clear();
for(int j=L[i];j<=R[i];j++) if(cnt[j]) insert(rt,j-L[i],0);
for(int j=L[i]-1;j;j--) add1(rt,0),v[i][j]=val[rt];
} int qu;scanf("%d",&qu);
while(qu--){
int l,r;scanf("%d%d",&l,&r);
printf("%s",(query(l,r))?"A":"B");
}
return 0;
}
做法四:根分+BIT
这是官方题解的做法(
考虑设一个阈值 \(K\) 并将询问离线下来。那么我们将所有位分为 \(<2^K\) 的位和 \(\ge 2^K\) 的位,对于 \(<2^K\) 的位就暴力存下所有数对 \(2^K\) 取模的值,查询时用个桶维护一下即可。对于 \(\ge 2^K\) 的位我们考虑扫描线,将询问挂在左端点处,可以注意到随着左端点的增大,每个 \(c_i-l\) 的 \(\ge 2^K\) 的位最多变化 \(\dfrac{m}{2^K}\) 次。我们就用 BIT 维护这些 \(c_i-l\) 的 \(\ge 2^K\) 位的异或值即可。取 \(K=\log_2(n\log n)\) 时复杂度最优。
时间复杂度 \(n\sqrt{n\log n}\)
有本事强制在线啊
做法 499122181:根分+分块
根据根号平衡的思想,我们把上面做法中 BIT 换成 \(\mathcal O(1)\) 单点加,\(\mathcal O(\sqrt{n})\) 求异或和的分块即可实现 \(n\sqrt{n}\) 的复杂度。
u1s1 官方题解为啥没想到根号平衡啊,不太懂
做法五:倍增
这是一个 nb 至极的倍增做法,不仅可以强制在线,而且复杂度还是 \(n\log n\) 的 %%%%%%%%
由于倍增本就基于二进制,而这题恰好涉及与下标有关的二进制运算,因此我们应尝试往倍增的方向考虑。我们设 \(f_{i,j}=\oplus_{c_k\in[j,j+2^i-1]}(c_k-j)\)。首先考虑怎么求 \(f_{i,j}\):考虑首先拿 \(f_{i-1,j}\) 去异或 \(f_{i-1,j+2^{i-1}}\),那么显然这两个东西异或在一起恰好包含了 \(2^0\sim 2^{i-2}\) 位的所有贡献,而显然 \(2^{i-1}\) 位会产生贡献,当且仅当有奇数个 \(c_k\in[j+2^{i-1},j+2^i)\),因此我们可以得到以下转移方程:
f[i][j]=f[i-1][j]^f[i-1][j+(1<<i-1)]^(((a[j+(1<<i)-1]-a[j+(1<<i-1)-1])&1)<<i-1);
其中 \(a\) 为桶的前缀和。
得出了 \(f\) 数组之后求解答案就非常 easy 了,考虑从大到小遍历每一位 \(i\),如果 \(l+2^i-1\le r\) 那么我们就将答案异或上 \(f_{i,l}\),即将 \([l,l+2^i-1]\) 的贡献计入答案,同时对于 \(c_k\in[l+2^i,r]\),它们也应对答案产生 \(2^i\) 的贡献,额外加上即可。
时间复杂度 \(n\log n\)。
const int LOG_N=18;
const int MAXN=2e5;
int n,m,qu,a[MAXN+5],f[LOG_N+2][MAXN+5];
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++) scanf("%d",&x),a[x]++;
for(int i=1;i<=m;i++) a[i]+=a[i-1];
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=m;j++)
f[i][j]=f[i-1][j]^f[i-1][j+(1<<i-1)]^(((a[j+(1<<i)-1]-a[j+(1<<i-1)-1])&1)<<i-1);
scanf("%d",&qu);while(qu--){
int l,r,cur,res=0;scanf("%d%d",&l,&r);cur=l;
for(int i=LOG_N;~i;i--){
if(r-cur+1>=(1<<i)) res^=f[i][cur],cur+=(1<<i),res^=((a[r]-a[cur-1])&1)<<i;
} printf("%s",(res)?"A":"B");
}
return 0;
}
Codeforces 1511G - Chips on a Board(01trie/倍增)的更多相关文章
- CF1511G Chips on a Board (倍增)
题面 原题题面 转化方便版题意: 有 n n n 堆石子,第 i i i 堆有 c i ∈ [ 1 , m ] c_i\in [1,m] ci∈[1,m] 个石子,有 q q q 次询问,每次询问给 ...
- 【CodeForces】983 E. NN country 树上倍增+二维数点
[题目]E. NN country [题意]给定n个点的树和m条链,q次询问一条链(a,b)最少被多少条给定的链覆盖.\(n,m,q \leq 2*10^5\). [算法]树上倍增+二维数点(树状数组 ...
- Codeforces 576D. Flights for Regular Customers(倍增floyd+bitset)
这破题调了我一天...错了一大堆细节T T 首先显然可以将边权先排序,然后逐个加进图中. 加进图后,倍增跑跑看能不能到达n,不能的话加新的边继续跑. 倍增的时候要预处理出h[i]表示转移矩阵的2^0~ ...
- Codeforces Round #518 (Div. 1) Computer Game 倍增+矩阵快速幂
接近于死亡的选手没有水平更博客,所以现在每五个月更一篇. 这道题呢,首先如果已经有权限升级了,那么后面肯定全部选的是 \(p_ib_i\) 最高的. 设这个值为 \(M=\max \limits_i ...
- codeforces 333B - Chips
注意:横向纵向交叉时,只要两条边不是正中的边(当n&1!=1),就可以余下两个chip. 代码里数组a[][]第二维下标 0表示横向边,1表示纵向边. #include<stdio.h& ...
- Codeforces 147B Smile House(DP预处理 + 倍增)
题目链接 Smile House 题意 给定一个$n$个点的有向图,求一个点数最少的环,使得边权之和$>0$,这里的环可以重复经过点和边. 满足 $n <= 300$ 首先答案肯 ...
- Codeforces 1244F. Chips
传送门 显然可以注意到连续的两个相同颜色的位置颜色是不会改变的,并且它还会把自己的颜色每秒往外扩展一个位置 同时对于 $BWBWBW...$ 这样的序列,它每个位置的颜色每一秒变化一次 然后可以发现, ...
- Codeforces 932 数组环构造 树上LCA倍增
A #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) #def ...
- CF1511G-Chips on a Board【倍增】
正题 题目链接:https://www.luogu.com.cn/problem/CF1511G 题目大意 给出\(n*m\)的棋盘上每一行有一个棋子,双方轮流操作可以把一个棋子向左移动若干步(不能不 ...
随机推荐
- 这样调优之后,单机也能扛下100W连接
1 模拟单机连接瓶颈 我们知道,通常启动一个服务端会绑定一个端口,例如8000端口,当然客户端连接端口是有限制的,除去最大端口65535和默认的1024端口及以下的端口,就只剩下1 024~65 53 ...
- 4个实验,彻底搞懂TCP连接的断开
前言 看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛.且慢,脑海中可以先尝试回答这几个问题: 四次挥手是谁发起的? 如果断电/断网了连接会断开吗? 什么情况下没 ...
- 并发编程从零开始(六)-BlockingDeque+CopyOnWrite
并发编程从零开始(六)-BlockingDeque+CopyOnWrite 5.2 BlockingDeque BlockingDeque定义了一个阻塞的双端队列接口: 该接口继承了BlockingQ ...
- docker run 的基本用法
docker run 命令用来创建并启动一个容器 语法:docker run [options] image [command] [args-] 示例:docker run -dit -v 别名:容器 ...
- 【BZOJ 1419】Red is good [概率DP]
我 是 Z Z 概率好玄啊(好吧是我太弱.jpg Description 桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元.可以随时停止翻 ...
- CSP-S 2021 遗言
感谢€€£,谢谢宁嘞! 第一题,€€£给了很多限制条件,什么"先到先得"."只有一个跑道",让它看起来很好做,然后来骗,来偷袭,广大"消费者" ...
- linux ar
转载:Linux ar命令 | 菜鸟教程 (runoob.com) Linux ar命令用于建立或修改备存文件,或是从备存文件中抽取文件. ar可让您集合许多文件,成为单一的备存文件.在备存文件中,所 ...
- cf12D Ball(MAP,排序,贪心思想)
题意: N位女士一起聚在一个舞厅.每位女士有三个特征值B,I,R.分别代表美貌,智慧,富有. 对于一位女士而言,如果存在一个女士的B,I,R都分别大于她自己的B,I,R.则她自己会自杀. 统计总共有多 ...
- cf 11B Jumping Jack(贪心,数学证明一下,,)
题意: 给一个数X. 起始点为坐标0.第1步跳1格,第2步跳2格,第3步跳3格,.....以此类推. 每次可以向左跳或向右跳. 问最少跳几步可以到坐标X. 思路: 假设X是正数. 最快逼近X的方法是不 ...
- Oracle 扩容表空间
system用户登陆oracle https://blog.csdn.net/zyingpei/article/details/88870693 首先查看表空间对应的数据文件位置以及大小 select ...