线段树模板二

1:扫描线+树状数组

扫描线排序:

按先y轴从小到大在按0,1

二维数点

题意

平面上有n个点(xi,yi)。回答q个询问,每个询问给定一个矩形[X1,X2]×[Y1,Y2],询问矩形里面有多少个点。

输入格式

第一行两个整数n,q(1≤n,q≤2×105)。
接下来n行,每行两个整数xi,yi(1≤xi,yi≤109)。
接下来q行,每行四个整数X1,X2,Y1,Y2(1≤X1≤X2≤109,1≤Y1≤Y2≤109)。

输出格式

对于每组询问,输出一个数表示答案。

样例输入

5 5
1 3
2 5
3 2
4 4
5 1
1 5 1 5
2 4 3 5
1 3 2 5
100 100 100 100
1 1 3 3

样例输出

5
2
3
0
1

点击查看代码
#include <bits/stdc++.h>

using namespace std;
#define int long long 

const int N = 1e6 + 10;
int n,m,k;
struct now{
	int a;
	int b;
	int c;
	int d;
};
vector<now>seg;
bool cmp(now l, now r)//排序注意点
{
	if(l.a==r.a)
	return l.c < r.c;
	return l.a < r.a;
}
int num[N],cnt[N];
void modify(int l,int r)
{
	for(;l <= k;)
	{
		num[l]+=r;
		l += (l&-l);
	}
}
int query(int l)
{
	int r = 0;
	for(;l;)
	{
		r += num[l];
		l -= (l&-l);
	}
	return r;
}
void solve()
{
	cin >> n >> m;
	vector<int> ans;
	for(int i = 1;i <= n;i ++)
	{
		int x,y;
		cin >> x >> y;
		seg.push_back({y,x,0,0});
		ans.push_back(x);
	}
	for(int i = 1;i <= m;i ++)
	{
		int x,y,xx,yy;
		cin >> x >> xx >> y >> yy;
		seg.push_back({yy,xx,1,i});
		seg.push_back({y-1,x-1,1,i});
		seg.push_back({y-1,xx,2,i});
		seg.push_back({yy,x-1,2,i});
	}
	sort(seg.begin(),seg.end(),cmp);
	sort(ans.begin(),ans.end());
	ans.erase(unique(ans.begin(),ans.end()),ans.end());
	k = seg.size();
	for(int i = 0;i < seg.size();i ++)
	{
		if(seg[i].c == 0)
		{
			int pos = lower_bound(ans.begin(),ans.end(),seg[i].b)-ans.begin()+1;
			modify(pos,1);//统计前缀和
		}
		else
		{
			int pos = upper_bound(ans.begin(),ans.end(),seg[i].b)-ans.begin();
			int res = query(pos);
			if(seg[i].c==1)
			cnt[seg[i].d]+=res;
			else
			cnt[seg[i].d]-=res;
		}
	}
	for(int i = 1;i <= n;i ++)
	cout << cnt[i] << '\n';
}
signed main()
{
	int tt = 1;
	//sc(tt);
	while(tt--)
	{
		solve();
	}
}

区间不同数之和

题意

有n个数a1,a2,…,an。有q组询问,每次给一个区间[l,r],求区间里不同的数字之和,也就是说同一个数字出现多次只算一次。

输入格式

第一行两个数n,q(1≤n,q≤2×1e5)。
接下来一行,一共n个数a1,a2,…,an(1≤ai≤n)。
接下来q行,每行两个整数l,r(1≤l≤r≤n)。

输出格式

对于每个询问,每行输出一个数表示答案。

样例输入

5 5
2 2 3 2 1
1 5
2 3
2 4
1 2
3 5

输出格式

6
5
5
2
6

点击查看代码
#include <bits/stdc++.h>

using namespace std;

#define int long long
const int N = 2e5 + 10;
int n,m;
int a[N],pre[N],last[N],ans[N],f[N];
struct now{
    int x;
    int val;
    int lei;
    int poi;
};
vector<now>seg;
bool cmp(now c,now b)
{
    if(c.x == b.x)
        return c.lei < b.lei;
    return c.x < b.x;
}
void add(int u,int d)
{
    for(;u<= N;)
    {
        f[u]+=d;
        u += (u&-u);
    }
}
int query(int u)
{
    int num = 0;
    for(;u;)
    {
        num += f[u];
        u -= (u&-u);
    }
    return num;
}
void solve()
{
    cin >> n >> m;
    for(int i = 1;i <= n;i ++)
        cin >> a[i];
   for(int i = 1;i <= n;i ++)
   {
       pre[i] = last[a[i]];
       last[a[i]] = i;
       seg.push_back({pre[i],i,0,a[i]});
   }
    for(int i = 1;i <= m;i ++)
    {
        int l,r;
        cin >> l >> r;
        seg.push_back({l-1,l-1,1,i});
        seg.push_back({l-1,r,2,i});
    }
    sort(seg.begin(),seg.end(),cmp);
    for(auto c:seg)
    {
        if(c.lei == 0)
        {
            add(c.val,c.poi);
        }
        else
        {
            if(c.lei == 1)
            ans[c.poi] -= query(c.val);
            else
              ans[c.poi] += query(c.val);  
        }
    }
    for(int i = 1;i <= m;i ++)
        cout << ans[i] << '\n';
}
signed main()
{
    solve();
}

2:字典树,权值线段树

异或第k小

给n个数字a1,a2,…,an。你要回答m个询问,每次给定两个数x,k,询问a1 xor x,a2 xor x,…,an xor x中从小到大排序中第k小的元素。

输入格式

第一行包含两个整数n,m。

第二行包含n个整数a1,a2,…,an。

接下来m行,每行两个数x,k(1≤k≤n),表示一组询问。

输出格式

一共m行,每行一个整数,表示上面数字里面的第k小。

### 样例输入

5 5
11 10 6 4 14
15 1
11 4
3 5
6 1
9 1

样例输出

1
13
13
0
2

点击查看代码
#include <bits/stdc++.h>

using namespace std;
#define int long long 

const int N = 1e6 + 10;
int n,m;
const int M = 30;
struct now{
	int w[3];
	int sz;
}seg[N*4];
int tot = 0,root;
void solve()
{
	cin >> n >> m;
	root = ++tot;
	for(int i = 1;i <= n;i++)
	{
		int x;
		cin >> x;
		int p = root;
		for(int j = M-1;j >= 0;j --)
		{
			seg[p].sz++;
			int u = (x>>j)&1;
			if(seg[p].w[u] == 0)
			seg[p].w[u] = ++tot;
			p = seg[p].w[u];
		}
		seg[p].sz++;
	}
	for(int i = 1;i <= m;i ++)
	{
		int x,y;
		cin >> x >> y;
		int p = root;
		int ans = 0;
		for(int j = M-1;j >= 0;j --)
		{
			int u = (x>>j)&1;
			if(seg[seg[p].w[u]].sz>=y)
			{
				p = seg[p].w[u];
			}
			else
			y -= seg[seg[p].w[u]].sz,ans^=(1<<j),p = seg[p].w[u^1];
		}
		cout << ans << '\n';
	}
}
signed main()
{
	int tt = 1;
	while(tt--)
	{
		solve();
	}
}

3:离线+线段树

mex

有一个长度为n的数组 a1,a2,…,an。

你要回答q个询问,每次给一个区间[l,r],询问这个区间内最小没有出现过的自然数。

输入格式

第一行两个数n,q(1≤n,q≤2×105)。

接下来一行,一共n个数a1,a2,…,an(0≤ai≤109)。

接下来q行,每行两个整数l,r(1≤l≤r≤n)。

输出格式

对于每个询问,每行输出一个数表示答案。

样例输入

5 5
2 1 0 2 1
3 3
2 3
2 4
1 2
3 5

输出格式

1
2
3
0
3

点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
typedef pair<int,int>  pii;
using ll = long long;
// 维护 第一个 最后出现的位置 小于l的
const int N = 2e5+5;
int tr[4*N],ans[N],pos[N],n,q,t,v[N];
vector<pii> que[N];
int op(int l,int r){
    return min(tr[l],tr[r]);
}
void change(int u,int l,int r,int pos,int val){
    if(l==r){
        tr[u]=val;
        return ;
    }
    int mid=l+r>>1;
    if(pos<=mid) change(u*2,l,mid,pos,val);
    else change(u*2+1,mid+1,r,pos,val);
    tr[u]=op(2*u,2*u+1);
}
int query(int u,int l,int r,int val){
    if(l==r) return l;
    int mid=l+r>>1;
    if(tr[2*u]<val) return query(u*2,l,mid,val);
    else return query(u*2+1,mid+1,r,val);
}
signed main() { 
    IO;
    cin >> n>>q;
    for (int i = 1; i <= n; i ++ ){
        cin >> v[i];
        v[i]=min(v[i],n+1);
    }
    int l,r;
    for (int i = 0; i < q; i ++ ){
        cin >> l>>r;
        que[r].push_back({l,i});
    }
    for (int i = 1; i <= n; i ++ ){
        change(1,0,n+1,v[i],i);
        for(auto &[a,b]:que[i]){
            ans[b]=query(1,0,n+1,a);
        }
    }
    for (int i = 0; i < q; i ++ )
    cout << ans[i]<<endl;
    return 0;
}
posted @ 2023-07-26 14:18  清初  阅读(21)  评论(0)    收藏  举报