数据结构 中级

扫描线:

矩形面积并 - 题目 - Daimayuan Online Judge

e0957e711b0a474fb939d0bd24d4fc5f.png

我们考虑用一条线(黑线)往上扫描的方式来求出答案  (右边为建树图)

 思路:离散化存入每一个区间段  样例中分成三个区间段 【1,2】,【2,3】,【3,4】

如图:

51a4a98f25ce4082b866bc6ee3996ad7.jpeg

 

struct ssss
{
	int a, b, c, d;
    //< 函数重载略
};
V<int>vx;
V<ssss>s; 

for (int i = 1; i <= n; i++)
	{
		int x1 = read(), x2 = read(), y1 = read(), y2 = read();
        //离散化
		vx.push_back(x1); vx.push_back(x2);
        // 因为是 y轴往上扫描,1第一维放 y  第二位是操作类型
		s.push_back({ y1,1,x1,x2 });
		s.push_back({ y2,-1,x1,x2 });
	}
    //排序 便于从下向上扫描区间
    sort(s.begin(), s.end(),cmp);
	sort(vx.begin(), vx.end());
	vx.erase(unique(vx.begin(), vx.end()), vx.end());

我们需要考虑如何维护 存在的矩形长度   ——————建树!!!

用建树的思路,记录每一个区间被覆盖的最小次数和长度  

//树中每个节点存储值
struct node
{
    // tag 标记   mi区间被覆盖最小次  当mi==0时候,说明此处没有矩形,无面积
	int tag, mi;
    //区间长度
	int cnt;
}e[N * 8];
// ×8 是因为离散化后 点数×2

 树中维护被覆盖最小次数的区间长度


void update(int id)
{
    e[id].mi = min(e[id * 2].mi, e[id * 2 + 1].mi);
    if (e[id * 2].mi > e[id * 2 + 1].mi)e[id].cnt = e[id * 2 + 1].cnt;
    else if(e[id * 2].mi<e[id * 2 + 1].mi)e[id].cnt = e[id * 2].cnt;
    else e[id].cnt = e[id*2].cnt + e[id * 2 + 1].cnt;
}

 

核心处理:

// m 个点形成 m-1个区间
int m = vx.size()-1;
//建树(模板 略)
build(1, 1, m);
// prey 记录前一个y的值 len记录一开始总区间长度
int prey=0;
int len = e[1].cnt;
for (auto &i : s)
	{
		int tl = len;
        //如果最小值是0 代表区间内部存在没有矩形的区域
		if (e[1].mi == 0)
			tl -= e[1].cnt;
		ans += (ll)tl * (i.a - prey);
		prey = i.a;

		// 例题图中1,2在【1,1】区域  2,3在【2,2】区域
		int x1 = lower_bound(vx.begin(), vx.end(), i.c) - vx.begin()+1;
		int x2 = lower_bound(vx.begin(), vx.end(), i.d) - vx.begin();
		if (x1 > x2)continue;
		//修改该范围 i.b==1 则插入该一段矩形 i.b==-1则删去
		change(1, 1, m, x1, x2, i.b);
	}
printf("%lld",ans);

 

 

树状数组

区间不同数之和 - 题目 - Daimayuan Online Judge

9e27e1c430914cf097b74905fe305cae.png

 采用离线的方式记录每一次的答案,一个区间不存在两个相同的数

可运用树状数组与前缀和的思想  对于【pos[i]+1,r】的区间和 加上 a[r]  

顺序遍历 r  对于 前 r-1区间的和不影响

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;


typedef unsigned long long ull;
#define V vector
#define pii pair<int,int>
using ll = long long;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;

int n;
ll c[N];

// pos[i] 记录上一个 i出现的下标 ans[i] 记录第i次的答案
int a[N];
int pos[N];
ll ans[N];
V<pair<int, int>>h[N];
void add(int x,ll d)
{
	for (; x<=n; x += x & (-x))
		c[x] +=d;
}

ll query(int x)
{
	ll ans = 0;
	for (; x; x -= x & (-x))
		ans +=c[x];
	return ans;
}
int main()
{
	int q;
	scanf("%d%d", &n,&q);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	int l, r;
	for (int i = 1; i <= q; i++)
	{
		scanf("%d %d", &l, &r);
		h[r].push_back({ l,i });
	}
	
	for (int r = 1; r <= n; r++)
	{
		int p = pos[a[r]];
		add(p + 1, a[r]);
		add(r + 1, -a[r]);
		pos[a[r]] =r;
		//printf("pos:%d\n", pos[a[r]]);
		for (auto i : h[r])
		{
			ans[i.second] += query(i.first);
		}
	}
	for (int i = 1; i <= q; i++)
		printf("%lld\n", ans[i]);
}

 

0 1字典树

异或第k小 - 题目 - Daimayuan Online Judge

 

74ff37be893a4c4eaf42ede45aa8c050.png

 

样例图解:

7c6dec71f30b466caba487591f34c58a.png

将每个数分解成二进制, 在树上类似二分找答案每一步走的值都最小 

若当前节点中存在至少k个不同的值,则进入,否则进入另一个儿子,k值也随之改变

const int M = 30;
struct node
{
	int s[2];
	int sz;
//二进制 题目数据最多30位
}e[N*31];
// tot 记录每个节点的编号,root根节点
int tot = 0, root;
int n, m;
int main()
{
	root = ++tot;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		int x; scanf("%d", &x);
		int p = root;
		for (int j = M-1; j >= 0; j--)
		{
			e[p].sz++;
			int w = (x>>j) & 1;
			if (e[p].s[w] == 0)e[p].s[w] = ++tot;
			p = e[p].s[w];
		}
		e[p].sz++;
	}
	for (int i = 1; i <= m; i++)
	{
		int x, k; scanf("%d%d", &x, &k);
		int p = root;
		int ans = 0;
		for (int j = M - 1; j >= 0; j--)
		{
			//取出 x 的第 j 位的数 走相同 异或最小
			int w = (x >> j) & 1;
			//若此时能满足第k小的存在,不用更新ans 因为相同 异或为0
			if (e[e[p].s[w]].sz >= k)
				p = e[p].s[w];
			else
			{
				//否则进入另一个节点,变成	k -= e[e[p].s[w]].sz 个最小;
				// 例如 :1 2 3 | 4 5 6   4为第4个最小,后半段的第一个最小 
				k -= e[e[p].s[w]].sz;
				ans ^= (1 << j);
				p = e[p].s[w ^ 1];
			}
		}
		printf("%d\n", ans);
	}

}

树上二分

mex - 题目 - Daimayuan Online Judge

38c0b73e20c047fb9ca7f7f4abd2c8e4.png

思路:用树记录 i  上一次出现的位置

!!!虽然a[i]的范围很大 但是大于n的数都可离散化为n+1,从而方便建树

任意询问一次  答案区间的范围 【0,n+1】,建立区间为【0,n+1】的树 每一个叶子代表第i个数上一次出现的位置

一开始,所有点上一次出现位置默认为 0

int b[N];
int n, q;
//树中记录上一次 最小的值 mi && mi<l
struct node
{
    int pos;
}a[N * 4];
void up(int id)
{
	a[id].pos = min(a[id * 2].pos, a[id * 2 + 1].pos);
}


void change(int id, int l, int r, int pos, int v)
{
	if (l == r)
	{
		a[id].pos = v;
		return;
	}
	else
	{
		int mid = l + r; mid /= 2;
		if (pos <= mid)change(id * 2, l, mid, pos, v);
		else change(id * 2 + 1, mid + 1, r, pos, v);
		//!!!
		up(id);
	}
}
//ql qr表示查询的区间  l r 表示啊a[id]对应的范围
//二分时候注意修改查询区间ql qr的值
int query(int id, int l, int r, int d)
{
	if (l == r)return l;
	int mid = l + r; mid /= 2;
   //若区间【l,mid】区间内的数上一次出现的位置存在小于d的则只搜索左边,因为答案求最小的
	if (a[id * 2].pos < d)return query(id * 2, l, mid,d);
	else return query(id * 2 + 1, mid + 1, r,d);
}
V<pii>v[N];
int ans[N];
int main()
{
	n = read(), q = read();
	for (int i = 1; i <= n; i++)
	{
		b[i] = read();
		b[i] = min(b[i], n + 1);
	}
	for (int i = 1; i <= q; i++)
	{
		int l = read(), r = read();
		v[r].push_back({ l,i });
	}
	for (int i = 0; i <= n; i++)
	{
         //更改b[i]这个数最新出现的位置
		change(1, 0, n + 1, b[i], i);
		for (pii s : v[i])
			ans[s.second] = query(1, 0, n + 1, s.first);
	}
	for (int i = 1; i <= q; i++)
		printf("%d\n", ans[i]);
}

线段树+二分

最开始有k个空闲的CPU,编号从0到k-1,然后有n个任务,每个任务有一个起始时间和持续时间,编号从1到n,每个任务都会去请求CPU来处理,假设这个任务的编号为i,那他会先去看 i % k的CPU有没有空闲,如果没有就去看 (i + 1) % k 有没有空闲,依次类推,当i加了k以后还没有CPU空闲,就说明这个时间所有的CPU都被占用了,那就忽略这个任务
求接收到最多任务的cpu编号

思路:

cpu 扩展到 2k-1 便于区间维护

运用线段树求出区间 cpu 结束时间的最小值 运用二分查询出 符合要求的下标 

#define _CRT_SECURE_NO_WARNINGS 1

#include<bits/stdc++.h>

using namespace std;

#define V vector
#define  int long long
const int inf = 1e18;
const int mod = 1e9 + 9;
const int N = 2e5 + 10;

//线段树+二分
int n, m;
int num[N];
int k;
int e[N * 4];

//维护区间最小值
inline void update(int u) {
    e[u] = min(e[u * 2], e[u * 2 + 1]);
}

inline void change(int u, int l, int r, int pos, int c) {
    if (l == r) {
        e[u] = c;
        return;
    }
    int mid = (l + r) / 2;
    if (pos <= mid)change(u * 2, l, mid, pos, c);
    if (pos > mid)change(u * 2 + 1, mid + 1, r, pos, c);
    update(u);
}

// 查询区间最小值
inline int getmin(int u, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr)
        return e[u];
    int mid = (l + r) / 2;
    int ans = inf;
    if (mid < qr)ans = min(ans, getmin(u * 2 + 1, mid + 1, r, ql, qr));
    if (mid >= ql)ans = min(ans, getmin(u * 2, l, mid, ql, qr));
    return ans;
}

inline int getpos(int l, int r, int s) {
    int f = 0;
    while (r >= l) {
        int mid = (r + l) / 2;
        // 优先找左边那一块
        if (getmin(1, 1, 2 * k-1, l, mid) <= s)
            f = mid, r = mid - 1;
        else l = mid + 1;
    }
    return f;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> k >> n;
    for (int i = 0; i < n; i++) {
        int s, d, l, r;
        cin >> s >> d;
        l = i % k + 1;
        r = i % k + k;
        // r-l+1==k
        if (getmin(1, 1, 2 * k-1 , l, r) > s)continue;
        // 二分 找到实际位
        int pos = getpos(l, r, s);
        num[(pos - 1) % k + 1] ++;
        //更新
        change(1, 1, 2 * k-1 , pos, s + d);
        change(1, 1, 2 * k-1, (pos + k - 1) % (2 * k) + 1, s + d);
    }
    int tmp = 0;
    V<int> tt;
    for (int i = 1; i <= k; i++)
        tmp = max(tmp, num[i]);
    for (int i = 1; i <= k; i++) {
        if (tmp == num[i])
            tt.push_back(i-1);
    }
    int sz = tt.size();
    for (int i = 0; i < sz; i++) {
        cout << tt[i];
        if (i != sz - 1)cout << " ";
    }

}




 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zzz0929_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值