扫描线:
矩形面积并 - 题目 - Daimayuan Online Judge
我们考虑用一条线(黑线)往上扫描的方式来求出答案 (右边为建树图)
思路:离散化存入每一个区间段 样例中分成三个区间段 【1,2】,【2,3】,【3,4】
如图:
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
采用离线的方式记录每一次的答案,一个区间不存在两个相同的数
可运用树状数组与前缀和的思想 对于【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
样例图解:
将每个数分解成二进制, 在树上类似二分找答案每一步走的值都最小
若当前节点中存在至少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
思路:用树记录 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 << " ";
}
}