数据结构模板笔记

数据结构

逆序对

归并
//白书
vector<int> A;
ll mc(vector<int> &a){
	int n=(int)a.size();
	if(n<=1) return 0;
	ll cnt=0;
	vector<int> b(a.begin(), a.begin()+n/2);
	vector<int> c(a.begin()+n/2, a.end());
	cnt+=mc(b);
	cnt+=mc(c);
	int ai=0, bi=0, ci=0;
	while(ai<n){
		if(bi<(int)b.size() && 
			(ci==(int)c.size() || b[bi]<=c[ci])){
			a[ai++]=b[bi++];
		}else{
			cnt+=n/2-bi;
			a[ai++]=c[ci++];
		}
	}
	return cnt;
}
树状数组

ans+=n-query(i)-1

O(n)找第K大

int find_k(int l, int r, int k) { // [1, n] 找第k大的数
	if(l==r) return a[l];
	int temp=a[l], s=l, t=r;
	while(s<t) {
		while(s<t && a[t]>=temp) t--;
		swap(a[s], a[t]);
		while(s<t && a[s]<temp) s++;
		swap(a[s], a[t]);
	}
	if(s-l+1==k) return a[s];
	else if(s-l+1>k) return find_k(1, s, k);
	else return find_k(s+1, r, k-(s-l+1));
}
// 调用 cout<<find_k(1, n, k)<<endl

树状数组

来源:https://www.cnblogs.com/RabbitHu/p/BIT.html

\(sum\)大小等于\(a\)即可,单点编号从1开始

单点修改 + 区间查询

储存数组\(a\)

\(sum[i]\)可改为别的,eg:\(mi[i]\)表示前缀最小值,则初始化为\(inf\)\(i\)为值为\(t\),则更新句改为\(mi[i]=min(mi[i], s)\);查询句改为\(ans=inf\),循环内为\(ans=min(ans, c[i])\)。因为,第一次遍历经过所有包含位置\(i\)的位置;第二次遍历,不重复的遍历一遍\(1\)\(i\)

所以,就由前缀和变为维护前缀最小值。

//#define lowbit(x) (x&(-x)) 
// 一维
void add(int x, int s) {
	for(int i=x; i<=n; i+=(i&-i)) sum[i]+=s;
}
int query(int x) {
	int res=0;
	for(int i=x; i>=1; i-=(i&-i)) res+=sum[i];
	return res;
}
//二维
void add(int x, int y, int s) {
	for(int i=x; i<=n; i+=(i&-i))
		for(int j=y; j<=m; j+=(j&-j))
			sum[i][j]+=s;
}
int query(int x, int y) {
	int res=0;
	for(int i=x; i>=1; i-=(i&-i)) {
		for(int j=y; j>=1; j-=(j&-j)) {
			res+=sum[i][j];
		}
	}
	return res;
}
区间修改 + 单点查询

储存差分\(d\)

修改:\([l,r]+k\Rightarrow d[l]+k,d[r+1]-k\)

查询:\(x\Rightarrow query(x)\)

二维:

\(d[i][j]=a[i][j]-(a[i-1][j]+a[i][j-1]-a[i-1][j-1])\)

修改:\([r1, r2]\times[c1,c2]+k\Rightarrow d[r1][c1]+k,d[r2][c2]+k,d[r1][c2]-k,d[r2][c1]-k\)

查询:\([r, c]\Rightarrow query(r,c)\)

d						a
0  0  0  0				0  0  0  0

0 +k  0 -k				0  k  k  k

0  0  0  0				0  k  k  k

0 -k  0 +k				0  k  k  k
void add(int p, int x){ //这个函数用来在树状数组中直接修改
    while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){ //给区间[l, r]加上x
    add(l, x), add(r + 1, -x);
}
int ask(int p){ //单点查询
    int res = 0;
    while(p) res += sum[p], p -= p & -p;
    return res;
}
//二维
void add(int x, int y, int z){ 
    int memo_y = y;
    while(x <= n){
        y = memo_y;
        while(y <= m)
            tree[x][y] += z, y += y & -y;
        x += x & -x;
    }
}
void range_add(int xa, int ya, int xb, int yb, int z){//闭区间
    add(xa, ya, z);
    add(xa, yb + 1, -z);
    add(xb + 1, ya, -z);
    add(xb + 1, yb + 1, z);
}
int ask(int x, int y){
    int res = 0, memo_y = y;
    while(x){
        y = memo_y;
        while(y)
            res += tree[x][y], y -= y & -y;
        x -= x & -x;
    }
    return res;
}
区间修改 + 区间查询

储存差分\(d,d2=d[i]\times i\)

修改:\([l,r]+k\Rightarrow d[l]+k,d[r+1]-k\)

\(d2[l]+k\times p,d2[r+1]-k\times p\)\(p\)\(pos\)

可将\(d,d2\)写入同一\(add(p,k)\)

查询:\(\sum_{i=0}^pa_i=(p+1)\times\sum_{i=0}^{p}d[i]-\sum_{i=0}^{p}d_2[i]\)

void add(ll p, ll x) {
	for(int i = p; i <= n; i += i & -i)
		sum1[i] += x, sum2[i] += x * p;	//修改
}
void range_add(ll l, ll r, ll x) {
	add(l, x), add(r + 1, -x);
}
ll ask(ll p) {
	ll res = 0;
	for(int i = p; i; i -= i & -i)
		res += (p + 1) * sum1[i] - sum2[i];//修改
	return res;
}
ll range_ask(ll l, ll r) {
	return ask(r) - ask(l - 1);
}

二维:

查询:\(\sum_{i=0}^{r}\sum_{j=0}^{c}a[i][j]=(r+1)\times(c+1)\sum_{i=0}^{r}\sum_{j=0}^{c}d[i][j]\)

\(-(c+1)\sum_{i=0}^{r}\sum_{j=0}^{c}d[i][j]\times i\)

\(-(r+1)\sum_{i=0}^{r}\sum_{j=0}^{c}d[i][j]\times j\)

\(+\sum_{i=0}^{r}\sum_{j=0}^{c}d[i][j]\times i\times j\)

void add(ll x, ll y, ll z) {
	for(int X = x; X <= n; X += X & -X)
		for(int Y = y; Y <= m; Y += Y & -Y) {
			t1[X][Y] += z;
			t2[X][Y] += z * x;
			t3[X][Y] += z * y;
			t4[X][Y] += z * x * y;
		}
}
void range_add(ll xa, ll ya, ll xb, ll yb, ll z) { //(xa, ya) 到 (xb, yb) 的矩形
	add(xa, ya, z);
	add(xa, yb + 1, -z);
	add(xb + 1, ya, -z);
	add(xb + 1, yb + 1, z);
}
ll ask(ll x, ll y) {
	ll res = 0;
	for(int i = x; i; i -= i & -i)
		for(int j = y; j; j -= j & -j)
			res += (x + 1) * (y + 1) * t1[i][j]
			       - (y + 1) * t2[i][j]
			       - (x + 1) * t3[i][j]
			       + t4[i][j];
	return res;
}
ll range_ask(ll xa, ll ya, ll xb, ll yb) {
	return ask(xb, yb) - ask(xb, ya - 1) - ask(xa - 1, yb) + ask(xa - 1, ya - 1);
}

rmq

// ST算法 求区间最值 预处理NlogN 查询O(1) 求解区间最值
// 查询次数较多时RMQ效率优于线段树 但是不支持修改操作
//从0开始存
int A[maxn], n;
int d[maxn][30], dd[maxn][30]; 
void init(int n) {
	for(int i=0; i<n; i++) d[i][0]=dd[i][0]=A[i];
	for(int j=1; (1<<j)<=n; j++) {
		for(int i=0; i+(1<<j)-1<n; i++) {
			d[i][j]=min(d[i][j-1], d[i+(1<<(j-1))][j-1]);
			dd[i][j]=max(dd[i][j-1], dd[i+(1<<(j-1))][j-1]);
		}
	}
}
int qmin(int L, int R) {//查[l, r]
	int k=0;
	while((1<<(k+1)) <= R-L+1) k++;
	return min(d[L][k], d[R-(1<<k)+1][k]);
}
int qmax(int L, int R) {
	int k=0;
	while((1<<(k+1)) <= R-L+1) k++;
	return max(dd[L][k], dd[R-(1<<k)+1][k]);
}

线段树

区间修改+区间查询

一些优化:\(a\times2\Rightarrow a<<1\)\(a\times2+1\Rightarrow a<<1|1\)

\(a/2\Rightarrow a>>1\)

#define Ls(a) (a<<1)
#define Rs(a) (a<<1|1)

注意:d描述查询区间和时,update中d[p]+=(t-s+1)*c不加(ll)可能会有溢出,检查了好久,呜呜呜。或者把修改值或覆盖值c一起改成ll。

区间修改+区间求和
ll a[maxn];//原数组
ll d[maxn<<2];//该点表示的区间查询值,描述查询功能
ll b[maxn<<2];//懒惰标记,pushdown,受修改方式影响 
void build(int s, int t, int p){
     b[p]=0;//区间修改+0时不用操作,所以可作为标记
	if (s==t){
		d[p]=a[s];
		return;
	}
	int m=(s+t)>>1;
	build(s, m, Ls(p)), build(m + 1, t, Rs(p));
	d[p]=d[Ls(p)]+d[Rs(p)];//区间求和
}
void update(int l, int r, int c, int s, int t, int p){
	if(l<=s && t<=r){
		d[p]+=(ll)(t-s+1)*c, b[p]+=c;//区间求和+区间修改
		return;
	}
	int m=(s+t)>>1;
	if(b[p] && s!=t){//pushdown
		d[Ls(p)]+=b[p]*(m-s+1), d[Rs(p)]+=b[p]*(t-m);
		b[Ls(p)]+=b[p], b[Rs(p)]+=b[p]; 
		b[p]=0;                                
	}
	if(l<=m) update(l, r, c, s, m, Ls(p));
	if(r>m) update(l, r, c, m + 1, t, Rs(p));
	d[p]=d[Ls(p)]+d[Rs(p)];//区间求和
}
ll getsum(int l, int r, int s, int t, int p){
	if(l<=s && t<=r) return d[p];
	int m=(s+t)>>1;
	if(b[p]){//pushdown
		d[Ls(p)]+=b[p]*(m-s+1), d[Rs(p)]+=b[p]*(t-m),
		b[Ls(p)]+=b[p], b[Rs(p)]+=b[p]; 
		b[p]=0;                                    
	}
	ll sum=0;
	if(l<=m) sum=getsum(l, r, s, m, Ls(p));//区间求和
	if(r>m) sum+=getsum(l, r, m + 1, t, Rs(p));//区间求和
	return sum;
}

测试程序:

int main() {
	int ty, l, r, x;
	int n=1000;
	build(1, n, 1);
	int T=1000;
	while(T--){
		ty=rand()%2;
		if(ty==0){
			l=rand()%n+1,r=rand()%n+1;
			if(l>r) swap(l, r);
			ll res=0;
			for(int i=l; i<=r; i++) res+=a[i];
			if(res!=getsum(l, r, 1, n, 1)) cout<<"1\n";
		}
		else{
			l=rand()%n+1,r=rand()%n+1;
			if(l>r) swap(l, r);
			x=rand()%(n*n);
			for(int i=l; i<=r; i++) a[i]+=x;
			update(l, r, x, 1, n, 1);
		}
	}
	return 0;
}
区间覆盖+区间求和

除了把有关区间修改的地方+=变为=,还有一点b(laze)初始化不再为0,0表示覆盖为0,应另取一个不会用于覆盖的数。

ll a[maxn];//原数组
ll d[maxn<<2];//该点表示的区间查询值,描述查询功能
ll b[maxn<<2];//懒惰标记,pushdown,受修改方式影响 
ll nev_use=inf;//初始化b[p]表示未标记时,要是一个不能访问的数 
void build(int s, int t, int p){
	b[p]=nev_use;//区间覆盖值!=inf,所以可作为标记
	if (s==t){
		d[p]=a[s];
		return;
	}
	int m=(s+t)>>1;
	build(s, m, Ls(p)), build(m + 1, t, Rs(p));
	d[p]=d[Ls(p)]+d[Rs(p)];//区间求和
}
void update(int l, int r, int c, int s, int t, int p){
	if(l<=s && t<=r){
		d[p]=(ll)(t-s+1)*c, b[p]=c;//区间求和+区间覆盖 
		return;
	}
	int m=(s+t)>>1;
	if(b[p]!=nev_use && s!=t){//pushdown
		d[Ls(p)]=b[p]*(m-s+1), d[Rs(p)]=b[p]*(t-m);
		b[Ls(p)]=b[p], b[Rs(p)]=b[p]; 
		b[p]=nev_use;//        
	}
	if(l<=m) update(l, r, c, s, m, Ls(p));
	if(r>m) update(l, r, c, m + 1, t, Rs(p));
	d[p]=d[Ls(p)]+d[Rs(p)];//区间求和
}
ll getsum(int l, int r, int s, int t, int p){
	if(l<=s && t<=r) return d[p];
	int m=(s+t)>>1;
	if(b[p]!=nev_use){//pushdown
		d[Ls(p)]=b[p]*(m-s+1), d[Rs(p)]=b[p]*(t-m),
		b[Ls(p)]=b[p], b[Rs(p)]=b[p]; 
		b[p]=nev_use;//                                   
	}
	ll sum=0;
	if(l<=m) sum=getsum(l, r, s, m, Ls(p));//区间求和
	if(r>m) sum+=getsum(l, r, m + 1, t, Rs(p));//区间求和
	return sum;
}
区间修改+区间最值
#define Ls(a) (a<<1)
#define Rs(a) (a<<1|1)
ll a[maxn];//原数组
ll d[maxn<<2];//该点表示的区间查询值,描述查询功能
ll b[maxn<<2];//懒惰标记,pushdown,受修改方式影响 
void build(int s, int t, int p){
    b[p]=0;//区间修改+0时不用操作,所以可作为标记
	if (s==t){
		d[p]=a[s];
		return;
	}
	int m=(s+t)>>1;
	build(s, m, Ls(p)), build(m + 1, t, Rs(p));
	d[p]=max(d[Ls(p)],d[Rs(p)]);//区间最值 
}
void update(int l, int r, int c, int s, int t, int p){
	if(l<=s && t<=r){
		d[p]+=c, b[p]+=c;//区间最值+区间修改
		return;
	}
	int m=(s+t)>>1;
	if(b[p] && s!=t){//pushdown
		d[Ls(p)]+=b[p], d[Rs(p)]+=b[p];
		b[Ls(p)]+=b[p], b[Rs(p)]+=b[p]; 
		b[p]=0;                      
	}
	if(l<=m) update(l, r, c, s, m, Ls(p));
	if(r>m) update(l, r, c, m + 1, t, Rs(p));
	d[p]=max(d[Ls(p)], d[Rs(p)]);//区间最值 
}
ll getmax(int l, int r, int s, int t, int p){
	if(l<=s && t<=r) return d[p];
	int m=(s+t)>>1;
	if(b[p]){//pushdown
		d[Ls(p)]+=b[p], d[Rs(p)]+=b[p],
		b[Ls(p)]+=b[p], b[Rs(p)]+=b[p]; 
		b[p]=0;                      
	}
	ll r1=-inf, r2=-inf;//min改inf
	if(l<=m) r1=getmax(l, r, s, m, Ls(p));//区间求和
	if(r>m) r2=getmax(l, r, m + 1, t, Rs(p));//区间求和
	return max(r1, r2);
}
区间覆盖+区间最值
ll a[maxn];//原数组
ll d[maxn<<2];//该点表示的区间查询值,描述查询功能
ll b[maxn<<2];//懒惰标记,pushdown,受修改方式影响 
ll nev_use=inf;//初始化b[p]表示未标记时,要是一个不能访问的数 
void build(int s, int t, int p){
	b[p]=nev_use;//区间覆盖值!=inf,所以可作为标记
	if (s==t){
		d[p]=a[s];
		return;
	}
	int m=(s+t)>>1;
	build(s, m, Ls(p)), build(m + 1, t, Rs(p));
	d[p]=max(d[Ls(p)], d[Rs(p)]);//区间最值 
}
void update(int l, int r, int c, int s, int t, int p){
	if(l<=s && t<=r){
		d[p]=c, b[p]=c;//区间最值+区间覆盖 
		return;
	}
	int m=(s+t)>>1;
	if(b[p]!=nev_use && s!=t){//pushdown
		d[Ls(p)]=b[p], d[Rs(p)]=b[p];
		b[Ls(p)]=b[p], b[Rs(p)]=b[p]; 
		b[p]=nev_use;//        
	}
	if(l<=m) update(l, r, c, s, m, Ls(p));
	if(r>m) update(l, r, c, m + 1, t, Rs(p));
	d[p]=max(d[Ls(p)], d[Rs(p)]);//区间最值 
}
ll getmax(int l, int r, int s, int t, int p){
	if(l<=s && t<=r) return d[p];
	int m=(s+t)>>1;
	if(b[p]!=nev_use){//pushdown
		d[Ls(p)]=b[p], d[Rs(p)]=b[p],
		b[Ls(p)]=b[p], b[Rs(p)]=b[p]; 
		b[p]=nev_use;//                                   
	}
	ll r1=-inf, r2=-inf;
	if(l<=m) r1=getmax(l, r, s, m, Ls(p));//区间最值 
	if(r>m) r2=getmax(l, r, m + 1, t, Rs(p));//区间最值 
	return max(r1, r2);
}

滑动窗口最值

单调队列,用数组模拟一个双端队列。

int q[maxn], a[maxn];
int n, k;
void getmin() {
  int head = 0, tail = 0;
  for (int i = 1; i < k; i++) {
    while (head <= tail && a[q[tail]] >= a[i]) tail--;
    q[++tail] = i;
  }
  for (int i = k; i <= n; i++) {
    while (head <= tail && a[q[tail]] >= a[i]) tail--;
    q[++tail] = i;
    while (q[head] <= i - k) head++;
    printf("%d ", a[q[head]]);
  }
}

void getmax() {
  int head = 0, tail = 0;
  for (int i = 1; i < k; i++) {
    while (head <= tail && a[q[tail]] <= a[i]) tail--;
    q[++tail] = i;
  }
  for (int i = k; i <= n; i++) {
    while (head <= tail && a[q[tail]] <= a[i]) tail--;
    q[++tail] = i;
    while (q[head] <= i - k) head++;
    printf("%d ", a[q[head]]);
  }
}

int main() {
  scanf("%d%d", &n, &k);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  getmin();
  printf("\n");
  getmax();
  printf("\n");
  return 0;
}

bitset

可以解决,短串\(s\)在长串\(a\)中,满足\(s_i?a_i,\ {i=0,\dots,s.size()}\),其中\(?\)为任意比较运算符的匹配数。为等于时就是KMP。

要求匹配串的b[i][j],为\(a[i]\)是否可匹配\(s[j]\)\(i+1\)行在\(i\)行右移一位,如:

\(\begin{split} b[i][0]\quad &b[i][1]\quad\quad\ \ \ b[i][2]\dots\\ &b[i+1][0]\quad b[i+1][1]\quad b[i+1][2]\dots \end{split}\)

当一列全为1时,ans++。例题:输入\(a,\ s\),其中比较运算符为\(\geq\)

该题还有一个关键点位快速求每个\(a[i]\)\(bitset\),因为\(s\)较短,求出按\(s\)离散的每个值的\(bitset\),在二分查找不在\(s\)中元素的\(bitset\)等于左边最近的值的\(bitset\)

const int maxn=40010;
const int INF=2000000010;
typedef long long ll;
int n, m, k, t;
int a[150010], b[maxn];
bitset<maxn> bbit[maxn];
vector< pair<int, int> > G;
int main() {
    cin>>n>>m;
    for(int i=1; i<=n; i++) cin>>a[i];
    for(int i=1; i<=m; i++) {
        cin>>b[i];
        G.push_back(make_pair(b[i], i));
    }
    G.push_back(make_pair(0, 0));
    sort(G.begin(), G.end());
    for(int i=1; i<=m; i++) {
        bbit[i] = bbit[i-1];
        bbit[i].set(G[i].second, 1);
    }
    bitset<maxn> ans;
    int cnt = 0;
    for(int i=n; i>=1; i--) {
        int x = upper_bound(G.begin(), G.end(), 
			make_pair(a[i], m+1)) - G.begin() - 1;
		cout<<x<<endl;
		cout<<bbit[x].to_string()<<endl<<endl<<endl;
        ans >>= 1;
        ans.set(m, 1);
        ans &= bbit[x];
        if(i<=n-m+1) cnt += ans[1];
    }
    cout<<cnt<<endl;
    return 0;
}

求MEX

给定数组a,求每个连续子区间的MEX值。不用\(n^2\)枚举区间。

利用改变树状数组,快速求前缀最小值(每次add只能向小于等于当前变化)。关键知道前缀最小值,如何求每两个相邻数间,是否存在完整的\(a[i]-1\)序列。eg:\(3\ 2\ 3 \ 1\),在任意\(3\ 3\)中间不存在\(1\ 2\),所以\(3\)不是一个MEX值,而在\(2\)到结束有\(1\),所以\(2\)是一个MEX值。

int n;//n为数组大小,同时为最大可能a[i]
int m[maxn], a[maxn];//m存前缀和最小值,a为原数组
int fa[maxn], r[maxn];//
bool mex[maxn];//标识i是否为MEX值
void add(int x, int s) {
	for(int i=x; i<=n; i+=(i&-i)) 
		m[i]=min(m[i], s);
}
int query(int x) {
	int res=inf;
	for(int i=x; i>=1; i-=(i&-i))
		res=min(res, m[i]);
	return res;
}
int main() {
	scanf("%d", &n);
	for(int i=1; i<=n; i++){
		scanf("%d", &a[i]);
		if(a[i]!=1) mex[1]=1;//1要单独处理,因为不看0
		fa[i]=r[a[i]];
		r[a[i]]=i;
		m[i]=inf;
	}
	for(int i=1; i<=n; i++){
		add(i, r[i]);
	}
    //对每个数,查看其r[i]的右端是否有完整i-1
    //判断的是最右边i到右边界
	for(int i=2; i<=n+1; i++){
		if(query(i-1)>r[i]) mex[i]=1;
	}
    //看依次删除r[i]后,就是每两个i间是否有完整i-1
    //判断的是每两个相邻i间是否有完整i-1;以及最左端i到0是否存在i-1
	for(int i=n; i>=1; i--){
		r[a[i]]=fa[i];
		add(a[i], r[a[i]]);
		if(a[i]==1) continue;
		if(query(a[i]-1)>r[a[i]]) mex[a[i]]=1;
	}
	return 0;
}

连续区间最大值

Kadane

就是常用的当前s是负是抛弃取0(贪心),每次更新res:

int res=0, s=0;
for(int i=0; i<n; i++){
	s+=a[i];
	res=max(res, s);
	s=max(s, 0);		
}
cout<<res;

它可以处理使用前缀和不能处理的\(res\)描述的变化,比如求\(res=\max(\sum[l,r]-\max[l,r])\)等问题,当\(|a_i|\)较小时直接枚举\(a_i\)

int ans=0;
for(int m=1; m<=30; m++) {
	int res=0, s=0;
	for(int i=0; i<n; i++) {
		if(a[i]>m) s-=inf;
		else s+=a[i];
		res=max(res, s);
		s=max(s, 0);
	}
	ans=max(ans, res-m);
}
cout<<ans;
前缀和

先求前缀和,对每个位置结束最大值为\(s_i-\min(s_j),j<i\)

for(int i=1 i<=n; i++) s[i]=s[i-1]+a[i];
int res=0, pr=0;
for(int i=1; i<=n; i++){
    res=max(res, s[i]-pr);
    pr=min(pr, s[i]);
}
cout<<res;

它可以处理kadane不能处理的区间形状描述的变化,比如求\(res=max(\sum[l,l+k-1]),1\le k\le c\),其中\(c\)为题目给出要求所取区间的最短长度:

int res=0, pr=0, w=0;
for(int i=c; i<=n; i++){
    res=max(res, s[i]-pr);
    pr=min(pr, s[++w]);
}
cout<<res;

区间众数

大于区间长度一半向上或向下取整,\(O(n\log n)\)

const int N = 300010;
int n, tot;
int rt[N];
struct Node {
	int ls, rs, sum;
} tree[N * 30];
int update(int cur, int l, int r, int pos, int val) {
	int p = ++tot;
	tree[p] = tree[cur];
	if(l == r) {
		tree[p].sum += val;
		return p;
	}
	int mid = l + r >> 1;
	if(pos <= mid)tree[p].ls = update(tree[cur].ls, l, mid, pos, val);
	else tree[p].rs = update(tree[cur].rs, mid + 1, r, pos, val);
	tree[p].sum = tree[tree[p].ls].sum + tree[tree[p].rs].sum;
	return p;
}
int query(int p, int q, int l, int r, int low) {
	if(l == r){
		//return tree[q].sum - tree[p].sum;//返回众数的cnt
		return l;//返回众数 
	}
	int mid = l + r >> 1;
	int lsum = tree[tree[q].ls].sum - tree[tree[p].ls].sum;
	int rsum = tree[tree[q].rs].sum - tree[tree[p].rs].sum;
	if(lsum > low)return query(tree[p].ls, tree[q].ls, l, mid, low);
	else if(rsum > low)return query(tree[p].rs, tree[q].rs, mid + 1, r, low);
	return -1;
}
int m, t, l, r;
int main() {
	cin>>n>>m;
	for(int i = 1; i <= n; i++) {
		cin>>t;//1<=a[i]<=n!!
		rt[i] = update(rt[i - 1], 1, n, t, 1);
	}
	while(m--){
		cin>>l>>r;
		int ans=query(rt[l-1], rt[r], 1, n, (r-l+1)/2);
		//ans=query(rt[l-1], rt[r], 1, n, (r-l+1+1)/2);也可以用 
		//query(rt[l-1], rt[r], 1, n, x)查询在(l,r)是否有数字出现次数严格大于x
		//返回那个数字不存在返回-1(因为严格大于一半所以只有一个) 
		cout<<ans<<"\n";
	}
	return 0;
}
真众数

还没交过题但是\(O(n\sqrt n)\),对\(3\times10^5\)\(1.6\times10^9\)不仅超时还超内存。

const int maxn = 50005, N = 305;
int sum[N][maxn], a[maxn], mul[N][N], cc[N][N];
int tmp[maxn];
//sum[i][j]统计前i块数字j出现的次数
//mul[i][j]计算众数个数最多且数字最少的数
//cc[i][j]计算众数最多个数
 
void solve ( ) {
	int n, Q;
	scanf ( "%d%d", &n, &Q );
	for ( int i = 0; i < n; i ++ ){
		scanf ( "%d", &a[i] );
	}
	int blo = ( int )sqrt ( n )+1;
	int cnt = ( n+blo-1 )/blo;
	for ( int i = 1; i <= blo; i ++ ) {
		for ( int j = 0; j < n; j ++ )
			sum[i][j] = sum[i-1][j];
		for ( int j = ( i-1 )*cnt; j < n && j < i*cnt; j ++ )
			sum[i][ a[j] ] ++;
	}
	for ( int i = 1; i <= blo; i ++ ) {
		for ( int j = i; j <= blo; j ++ ) {
			cc[i][j] = cc[i][j-1];	//注意初始化
			mul[i][j] = mul[i][j-1];
			for ( int k = ( j-1 )*cnt; k < n && k < j*cnt; k ++ ) {
				int x = sum[j][ a[k] ]-sum[i-1][ a[k] ];
				if ( x > cc[i][j] || x == cc[i][j] && mul[i][j] > a[k] ) {
					cc[i][j] = x;
					mul[i][j] = a[k];
				}
			}
		}
	}
	int L, R, ans, anspos;
	//ans次数最多,anspos次数最多且数字最小
	while ( Q -- ) {
		scanf ( "%d%d", &L, &R );
		L--, R--;
		int x = L/cnt+1, y = R/cnt+1;
		ans = cc[x+1][y-1], anspos = mul[x+1][y-1];
		for ( int i = L; i <= R && i < x*cnt; i ++ )
			tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
		for ( int i = max ( L, ( y-1 )*cnt ); i <= R; i ++ )
			tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
		for ( int i = L; i < x*cnt && i <= R; i ++ ) {
			tmp[ a[i] ] ++;
			if ( tmp[ a[i] ] > ans || tmp[ a[i] ] == ans && a[i] < anspos ) {
				ans = tmp[ a[i] ];
				anspos = a[i];
			}
		}
		if ( x != y ) {	//同块处理
			for ( int i = ( y-1 )*cnt; i <= R; i ++ ) {
				tmp[ a[i] ] ++;
				if ( tmp[ a[i] ] > ans || tmp[ a[i] ] == ans && a[i] < anspos ) {
					ans = tmp[ a[i] ];
					anspos = a[i];
				}
			}
		}
		printf ( "%d %d %d\n", anspos, ans, a[anspos-1]);//从0开始存 
	}
}
int main ( ) {
	solve ( );
	return 0;
}

枚举子集

状压dp时枚举子集,从大到小,有\(w\)\(1\)就有\(2^w-1\)个非空子集,eg:\(10110\rightarrow10100\rightarrow10010\rightarrow10000\rightarrow00110\rightarrow00100\rightarrow00010\)

for(int i=s; i>0; i=(i-1)&s){
	int j=s^i;//j=s&(~i),j为补集
}

给出一些集合(没重复元素),问是否存在选集合,恰好每个元素(共\(k\)个)出现一次,eg:\(100,010,001\)可行,\(100,110,001\)\(1000,0100,0010\)都不行。

例题:https://codeforces.com/contest/1243/problem/E,蜜汁复杂度

//dp[i]提前存好了存在的集合
for(int i=0; i<(1<<k); i++){
	if(dp[i]) continue;
	for(int j=i; j>0; j=(j-1)&i){//只用考虑2个不用考虑3,4,5...个因为i是从小到大
		if(dp[j] && dp[j^i]) dp[i]=1;
	}
}

倍增

朝一个方向倍增,例如从节点向根上走的简单路径上,求离根最近的满足一定条件的点。这样不好二分,因为只能向一个方向走,考虑倍增。可以在线。

//只能在路径尾u下面增加点v,点不包含0时,fa[maxn][_log]就可以初始化为0
fa[v][0]=u;
for(int i=1; i<=_log; i++){
	fa[v][i]=fa[fa[v][i-1]][i-1];
}
//给出节点u,向上找出离最近的点(上面一定不满足,下面一定都满足)
for(int i=_log; i>=0; i--){
	if(fa[u][i] && a[fa[u][i]]){//a表示满足条件
		u=fa[u][i];
	}
}

区间合并

\(1\)\(n\)总覆盖长度,如出现\((1,1)\)\(ans++\)

树状数组:区间求改+单点查询

int n, sum[maxn];//区间1-n
void add(int p, int x){
    while(p <= n) sum[p] += x, p += p & -p;
}
void radd(int l, int r, int x){
    add(l, x), add(r + 1, -x);
}
int ask(int p){
    int res = 0;
    while(p) res += sum[p], p -= p & -p;
    return res;
}
int m;//线段个数
int l[maxn], r[maxn], d[maxn];//对d[i]大于x的线段才计算,每段都计算把radd前if去掉就好
int cal(int x){
	memset(sum, 0, sizeof(sum));
	for(int i=1; i<=m; i++){
		if(d[i]>x) radd(l[i], r[i], 1);
	}
	int ans=0;
	for(int i=1; i<=n; i++){
		if(ask(i)) ans++;
	}
	return ans;
}

不要logn,直接on

int m, n;//线段个数,区间1-n
vector<pair<int, int>> G[maxn];//G[l]=make_pair(r, d);
//d可以用于动态查询,只有di>x的线段才使用
int ck(int x) {
	int l=1, ans=0;
	while(l<=n) {
		int tl=l, r=l, fl=1;
		while(l<=r) {
			for(auto i: G[l]) {//对一个l遍历每条线段
				if(i.second<=x) continue;
				r=max(r, i.first);
				fl=0;//当tl=r=l+1时,是否有这条线段,有对(r,r)就要ans++
			}
			l++;
		}
		ans+=l-tl-fl;//[tl, l)为一个区间,fl表示tl=l时该区间是否出现
	}
	return ans;//返回覆盖数
}
posted @ 2021-03-02 15:17  bqlp  阅读(58)  评论(0)    收藏  举报