您的位置:首页 > 其它

国庆中秋假期训练

2017-10-07 16:03 253 查看
这几天真的是很赶,走亲戚出去吃饭什么的,一浪费就是一天一下午的时间。。。。。。。

也着急做题,,,,,,而且还都是些没接触的东西,加上线段树和树状数组这块学的不是很好。。。。

第一个就是 传说中能解决一切区间问题的莫队算法,这个算法早就想看一直拖到现在。

用来处理一类无修改的离线区间询问问题

问题:有n个数组成一个序列,有m个形如询问L, R的询问,每次询问需要回答区间内至少出现2次的数有哪些。

/*作者:张瑯小强

链接:https://www.zhihu.com/question/27316467/answer/130423804

*/

一种直观的办法是按照左端点排序,再按照右端点排序。但是这样的表现不好。特别是面对精心设计的数据,这样方法表现得很差。

举个例子,有6个询问如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。  

这个数据已经按照左端点排序了。用上述方法处理时,左端点会移动6次,右端点会移动移动98+97+95+98+95=483次。

右端点大幅度地来回移动,严重影响了时间复杂度——排序的复杂度是O(mlogm),所有左端点移动次数仅为为O(n),但右端点每个询问移动O(n),共有m个询问,故总移动次数为O(nm),移动总数为O(mlogm + nm)。运行时间上界并没有减少。  

其实我们稍微改变一下询问处理的顺序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。  

左端点移动次数为2+2+1+2+2=9次,比原来稍多。右端点移动次数为2+3+95+3+1=104,右端点的移动次数大大降低了。  

上面的过程启发我们:

①我们不应该严格按照升序排序,而是根据需要灵活一点的排序方法;

②如果适当减少右端点移动次数,即使稍微增多一点左端点移动次数,在总的复杂度上看划算。

在排序时,我们并不是按照左右端点严格升序排序询问,而只是令其左右端点处于“大概是升序”的状态。

具体的方法是,把所有的区间划分为不同的块,将每个询问按照左端点的所在块序号排序,左端点块一样则按照右端点排序。

这就是莫队算法!!!!!!!!!

莫队算法首先将整个序列分成√n个块(同样,只是概念上分的块,实际上我们并不需要严格存储块),接着将每个询问按照块序号排序(一样则按照右端点排序)。之后,我们从排序后第一个询问开始,逐个
4000
计算答案。

题意已知一个长度为n的数列 (0 ≤ ai ≤ 1 000 000) ,给m个区间,问每个区间有多少个子区间xor和为k 

(1 ≤ n, m ≤ 100 000, 0 ≤ k ≤ 1 000 000)

莫队算法 

如果你知道了[L,R]的答案。你可以在O(1)的时间下得到[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案的话。就可以使用莫队算法。

先对序列分块。然后对于所有询问按照L所在块的大小排序。如果一样再按照R排序。然后按照排序后的顺序计算。复杂度O(n^1.5)

对于本题

我们设定 sum[i] 为[1, i]区间内的异或和,对于区间[a, b]的异或和为sum[b] ^ sum[a-1]。如果区间 [a, b] 的异或和为k,则有sum[b] ^ sum[a-1] == k,由于异或的性质可以推论 出:sum[b] ^ k == sum[a-1],sum[a-1] ^ k == sum[b]。

需要注意的几个地方

1 结果可能超int

2 区间[i,j]的异或和是sum[i-1]^sum[j]的结果,所以要保存i-1到j的异或值

3 l和r以及flag[0]的初值,flag[i]代表前缀和的数量

4 add()和dele()函数的写法

#include <cmath>  

#include <queue>  

#include <cstdio>  

#include <cstring>  

#include <cstdlib>  

#include <iostream>  

#include <algorithm>  

#define LL long long  

#define INF 0x3f3f3f3f  

using namespace std;  

const int maxn = 2e6+7;  

struct node {int l,r,id;}q[maxn];  

int a[maxn],pos[maxn];  

LL ans[maxn],sum[maxn];  

int n,m,k;  

LL Ans=0;  

bool cmp(node a,node b)  

{  

    if(pos[a.l]!=pos[b.l]) return pos[a.l]<pos[b.l];  

    return a.r<b.r;  

}  

void add(int x)  

{  

    Ans += flag[a[x]^k];  

    flag[a[x]]++;  

}  

void dele(int x)  

{  

    <span style="font-family:Arial, Helvetica, sans-serif;">flag[a[x]]--;</span>  

    Ans -= flag[a[x]^k];  

}  

int main()  

{  

    int i;  

    while(~scanf("%d%d%d",&n,&m,&k))  

    {  

        Ans=0;memset(flag,0,sizeof(flag));  

        int ss=sqrt(n);flag[0]=1;  

        for(i=1;i<=n;i++) scanf("%d",a+i),a[i]^=a[i-1],pos[i]=i/ss;  

        for(i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;  

        sort(q+1,q+1+m,cmp);  

        int l=1,r=0;  

        for(i=1;i<=m;i++)  

        {  

            while(q[i].l<l){l--;add(l-1);}  

            while(q[i].l>l){dele(l-1);l++;}  

            while(q[i].r<r){dele(r);r--;}  

            while(q[i].r>r){r++;add(r);}  

            ans[q[i].id]=Ans;  

        }  

        for(i=1;i<=m;i++) printf("%I64d\n",ans[i]);  

    }  

    return 0;  

}  

还有就是线段树离散化,有关离散化的题我是放在后面做的

对于离散化,初步理解就是因为给的范围无限大或过大无法用数组直接表示,所以进行转换

eg 范围[1,6] [1.7] [2,10] [8 18] 将各点排序

1 1 2 6 7 8 10 18   离散后对应的坐标为

 1  2 3 4 5 6  7    再根据原来的点把它们对应起来,则离散后坐标为

[1,3] [1,4] [2,6] [5,7]

求解这题的方法 是从后往前贴 如果发现这块区域被完全覆盖了,那就返回。

离散化是一种常用的技巧,有时数据范围太大,可以用来放缩到我们能处理的范围 因为其中需排序的数的范围0---

999999999;显然数组不肯能这么大;而N的最大范围是500000;故给出的数一定可以与1.。。。N建立一个一一映射;

 

这里用一个结构体

struct Node

 {

      int v,order;

 }a[510000];

和一个数组aa[510000];

 

其中v就是原输入的值,order是下标;然后对结构体按v从小到大排序;此时,v和结构体的下标就是一个一一对应关系,而且

满足原来的大小关系;

for(i=1;i<=N;i++)

     aa[a[i].order]=i;

然后a数组就存储了原来所有的大小信息;比如 9 1 0 5 4 ------- 离散后aa数组就是 5 2 1 4 3;具体的过程可以自己

用笔写写就好了。

离散之后,怎么使用离散后的结果数组来进行树状数组操作? 

如果数据不是很大,可以一个个插入到树状数组中,每插入一个数,统计比他小的数的个数,对应的逆序为 i- getsum(aa

[i]),其中i为当前已经插入的数的个数,getsum(aa[i])为比aa[i]小的数的个数,i- sum(aa[i]) 即比aa[i]大的个数,

即逆序的个数但如果数据比较大,就必须采用离散化方法。

树状数组用于非前缀区间技巧

在一些情况下,可以使用树状数组来完成线段树的功能。

以RMQ问题为例,很容易想到的方法是,先按原始方式建立树状数组,对于一个元素bit[x],首先看x-lowbit(x)+1超没超出查询范围的左边界,没超出当然是最好了,因为这就跟一般树状数组一样处理就行了,直接使用这个值;但若是超出了,那肯定不能用这个元素了,没办法那就退一步吧,直接使用原数组的值,然后问题中的右边界不久小了1吗?虽然只少了1,但至少还是少了吧,然后接下来就是重复这个过程直到完全查询完整个区间(只要x-lowbit(x)+1刚好等于左边界就行了)。这样,树状数组也就不再依赖于区间减法操作,能够完成一些更复杂的操作,在某些场合替代线段树。

至于复杂度分析,不用说该是比lgN大了,因为遇到超出左边界的情况只能把右边界减1,这显然比起原始的树状数组就慢了,不过也没事。

。。。。。。。。哇
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: