从几场模拟考试看一类分块算法
退役前最后一篇博客。。。希望我滚回去高考完还能记得一些OI知识
最近考试我们有一道这样的题目:
如果有两个序列A和B,对于A序列中的每一个元素,都在B序列中选一个大于等于它的最小的值。若A序列选出的不同的B序列元素个数为奇数则称之为好的。现在给出n个序列S1..n,对于每两个序列Si,Sj(i<j)进行一次判断,问总共有多少个好的序列对。 n<=5000,A,B序列的元素大小不超过50。
首先std给出了这样的题解:
因为所有的位置范围是[1,50],我们可以用位运算加速统计。将序列 b 转换成一个二进制数B,将序列 a 转换成一个二进制数 A,我们要统计的是 A 中的某些特定区间内是否有二进制 1。令B ′ = B ≪ 1,则B ′ &(A + (A|~B ′ ))中的二进制 1 即表示A的对应区间内是否有 1,统计其二进制 1 的奇偶性即可。
看起来十分简洁但是对我这种NOIP(普及组初赛)水平选手却并不容易想到,因此考试的时候我得到了一个不怎么优的做法:分块
分析:
首先我们可以发现一共有C(n,2)种选法,这个数量大约是1.25E7。那么对于每个的判断我们有8次操作的时间完成。
问题显然可以转化为二进制,那么我们可以得到一个二进制下的50位数。
位数不多,情况只有01两种,所以考虑采用分块。
令块的大小为S,那么在块内我们总共有4^S种方案,每种方案我们可以在O(S)的时间内处理出它填到了块内B序列中的哪些位置,以及是否有剩余的没填的。顺便对于每一种B序列的块,处理左边第一个1在哪里。
换句话说,预处理的时间复杂度为O(4^S*S)。
对于每两个序列,预先分好块,不足的补满,最大块数为5块。从左到右扫描每一块,并查表得到答案,并和上一个的答案合并,合并的时候要考虑上一块是否多余了一些。
可以发现每一次询问的时间复杂度为O(m/S),共O(n^2)组询问,总时间O(n^2*m/S)。
总的来说,时间复杂度为O(4^S*S+n^2*m/S)。
分析分块大小,通过解方程我们知道S=10时比较优,实测可以过,最大数据0.2秒。
奉上代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 int n; 5 int num[5010]; 6 int a[5010][53]; 7 long long res[5010][7]; 8 long long pp[1050][1050]; 9 long long rm[1050][1050]; 10 int frt[1050]; 11 12 void read(){ 13 scanf("%d",&n); 14 for(int i=1;i<=n;i++){ 15 scanf("%d",&num[i]); 16 for(int j=1;j<=num[i];j++){ 17 scanf("%d",&a[i][j]); 18 } 19 for(int j=1;j<=num[i];j++){ 20 res[i][0] |= (1ll<<a[i][j]-1); 21 } 22 for(int j=1;j<=5;j++){ 23 for(int k=0;k<10;k++){ 24 if((1ll<<(j-1)*10+k)&res[i][0]){ 25 res[i][j] |= (1<<k); 26 } 27 } 28 } 29 } 30 } 31 32 void deal(int tot,int tt){ 33 int j = 0; for(;j<10;j++) if(tt&(1<<j)) break; 34 for(int i=0;i<10;i++){ 35 if((tot&(1<<i)) && j < 10) pp[tot][tt] |= (1<<j); 36 if((tot&(1<<i)) && j >= 10) rm[tot][tt] = 1; 37 if(i+1 > j){ j++; while(j < 10){if(tt&(1<<j))break; j++;} } 38 } 39 } 40 41 void dfs2(int now,int tot,int tt){ 42 if(now > 10){ deal(tot,tt); } 43 else{ 44 dfs2(now+1,tot,tt|(1<<now-1)); 45 dfs2(now+1,tot,tt); 46 } 47 } 48 49 void dfs1(int now,int tot){ 50 if(now > 10){ 51 for(int i=0;i<=10;i++){if((tot&(1<<i))||i==10){frt[tot] = i;break;}} 52 dfs2(1,tot,0); 53 }else{ 54 dfs1(now+1,tot|(1<<now-1)); 55 dfs1(now+1,tot); 56 } 57 } 58 59 int anss = 0; 60 void solve(int l,int r){ 61 long long om = 0,ro = 0; 62 for(int i=1;i<=5;i++){ 63 om = om+(pp[res[l][i]][res[r][i]]<<10*(i-1)); 64 if(ro && frt[res[r][i]] != 10){ 65 int ft = frt[res[r][i]]; 66 om |= (1ll<<ft+10*(i-1)); 67 ro = 0; 68 } 69 ro |= rm[res[l][i]][res[r][i]]; 70 } 71 anss += __builtin_parityll(om); 72 } 73 74 void work(){ 75 dfs1(1,0); 76 for(int i=1;i<=n;i++){ 77 for(int j=i+1;j<=n;j++){ 78 solve(i,j); 79 } 80 } 81 printf("%d\n",anss); 82 } 83 84 int main(){85 read(); 86 work(); 87 return 0; 88 }
更进一步地灵活运用它,我们再来看一道题。
维护一个序列,支持单点修改,区间和的phi值查询以及区间乘法的phi值查询。其中序列长度n为50000,值的大小ai为40000,询问个数q为100000
题解给出了一种基于二分的方法,但是对我这种NOIP(普及组初赛)水平选手却并不容易想到,因此考试的时候我得到了一个不怎么优的做法:分块。
对于加法我们比较容易想到一个N^1.5的枚举质因数的做法。所以我们只考虑修改和乘法。
考虑从phi的定义入手,那么我们只需要考虑l到r之间哪些素数是出现过的,由于ai在40000以内,所以我们发现质数个数接近40000/ln(40000)个。
使用bitset维护合并,建一棵线段树维护区间内的bitset的并集。
这样子,单次修改操作会改变logn个bitset的内容,时间复杂度为O( (logn*ai) / (ln(ai)*w) ),我们可以认为ai和n同阶,时间复杂度为O(n/w).
考虑每次区间乘法操作,需要合并logn个bitset,这一部分时间复杂度同上。
另外的,我们需要对合并结果进行处理,也就是对bitset的每个位对应的p都处理一次,这个操作的时间复杂度O(n/ln(n)).
当三种操作平均分布的时候,我们的时间复杂度为O(q*(sqrt(n)+n/w+n/ln(n))).
这个算法有优化的空间,我们发现n/ln(n)是我们算法速度的瓶颈。带入数字可以发现,当操作不随机的时候,我们可以构造一个数据,使得总运行次数达到3.7E+8次。其中光是n/ln(n)就占了3E+8。
实际上,我们有大量的运算是重复的。
下面我举一个例子。
考虑01串
1011011100001010
1011010100011010
我们将它按每4位算一组,当我们计算这两个01串的时候,我们要计算8次。但如果我们预处理出了每4位对应的答案,那么我们只要计算6次。
这在q很少的时候优化不明显,但是当q的级别很大的时候,那么这样可以节省很多运算。
对于上面的2操作得到的最终的并,我们可以将它每16位分为一组,通过DP预处理出答案。DP的转移是O(1)的,所以DP预处理的时间复杂度为O(n*2^16/16)。
接着我们对于最终的并,每16位作为一个整体在表中直接查询答案,时间复杂度可以在原来的基础上除以16,令16=v,2操作的时间复杂度被降低为O(n/w+n/ln/v),同样的数据只用计算1E+8次。
值得注意的是,这并不是一个玄学的优化,而是一类有体系的优化方法。
这种方法同样也被运用在RMQ问题中,有兴趣的可以学一下
- 2018腾讯模拟考试算法题(求该数是多少对质数相加的和)
- Linux编程:模拟进程调度算法
- FZU 2254 英语考试-初入算法 最小生成树
- 【2016普及组模拟考试】04 搜索 tribe(部落卫队)
- 内存管理伙伴算法 模拟程序
- 【NOIP普及组】2016模拟考试(10.29)——排座椅
- 特长生模拟——12年东莞市特长生考试
- NOIP2016#模拟考试 Day.2# T3 王位继承
- javascript 模拟亚马逊左侧导航算法的tab选项卡,支持四个方向,支持tab键切换,兼容各浏览器
- NOIP模拟 NYG的背包 [高山算法]
- DL:RBM学习算法——Gibbs采样、变分方法、对比散度、模拟退火
- NOIP2017赛前模拟(2017.10.31)考试总结
- HDU 1281 棋盘游戏(二分图匹配+匈牙利算法+模拟)
- Zend PHP5认证模拟考试以及答案(一)
- 模拟简单距离向量算法的更新——计算机网络作业
- 【NOIP普及组】2016模拟考试(11.8)——公路
- 算法入门--模拟彩票
- 16.1112 模拟考试 T1
- 工作笔记——一键配招算法思路,模拟退火
- 【算法】KMP算法超详好懂理解(附全程序模拟过程)