羊开茗,快来学习贤断术
使用权值线段树(因为我一开始用普通线段树超时了)
对每一个位置
,求区间
和
中有多少个比
大的数和比
小的数
总代码:
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define HelloWorld IOS;
const int N = 200000 + 100;
struct Node{
int l, r;
int cnt;
}tr[4 * N];
int n, a[N];
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, 0};
return ;
}
else{
tr[u] = {l, r, 0};
int mid = (tr[u].l + tr[u].r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int x){
if(tr[u].l == tr[u].r){
tr[u].cnt ++;
return ;
}
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x);
else modify(u << 1 | 1, x);
tr[u].cnt = (tr[u << 1].cnt + tr[u << 1 | 1].cnt);
}
}
int query(int u, int l, int r){
if(l > r) return 0;
if(l <= tr[u].l && tr[u].r <= r) return tr[u].cnt;
else{
int mid = (tr[u].l + tr[u].r) >> 1;
int sum = 0;
if(l <= mid) sum += query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
}
void solve(){
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
build(1, 1, n);
vector<int> pre_max(n + 10), pre_min(n + 10);
for(int i = 1; i <= n; i ++){
pre_max[i] = query(1, a[i] + 1, n);
pre_min[i] = query(1, 1, a[i] - 1);
modify(1, a[i]);
}
build(1, 1, n);
vector<int> suf_max(n + 10), suf_min(n + 10);
for(int i = n; i >= 1; i --){
suf_max[i] = query(1, a[i] + 1, n);
suf_min[i] = query(1, 1, a[i] - 1);
modify(1, a[i]);
}
int ans1 = 0, ans2 = 0;
for(int i = 1; i <= n; i ++){
ans1 += pre_max[i] * suf_max[i];
ans2 += pre_min[i] * suf_min[i];
}
cout << ans1 << " " << ans2 << endl;
return ;
}
signed main(){
HelloWorld;
int tt = 1;
while(tt --) solve();
return 0;
}
模板,不贴代码了
倒着枚举:样例
,
对于
,说明前面有
头牛比第
头的牛的身高低,所以第
头的牛的身高是第
小的,即
对于
,说明前面有
头牛比第
头的牛的身高低,所以第
头的牛的身高是第
小的,应该为
么?不是的,因为前面的
已经被用了,所以在目前的所有可以选择的身高:
里面,第
小的是
.
同样,对于
,那么第
头的牛的身高是第
小的,目前所有可以选择的身高为
,所以身高为
依旧可以使用权值线段树,从后枚举答案以及进行删除操作
总代码:
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define HelloWorld IOS;
const int N = 8000 + 100;
int n, a[N];
struct Node{
int l, r;
int cnt;
}tr[4 * N];
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, 0};
return ;
}
else{
tr[u] = {l, r, 0};
int mid = (tr[u].l + tr[u].r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int x, int v){
if(tr[u].l == tr[u].r){
tr[u].cnt += v;
return ;
}
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt;
}
}
int query(int u, int x){
if(tr[u].l == tr[u].r) return tr[u].l;
else{
if(tr[u << 1].cnt >= x) return query(u << 1, x);
else return query(u << 1 | 1, x - tr[u << 1].cnt);
}
}
void solve(){
cin >> n;
build(1, 1, n);
for(int i = 1; i <= n; i ++) modify(1, i, 1);
for(int i = 2; i <= n; i ++) cin >> a[i];
vector<int> ans(n + 10);
for(int i = n; i >= 1; i --){
ans[i] = query(1, a[i] + 1);
modify(1, ans[i], -1);
}
for(int i = 1; i <= n; i ++) cout << ans[i] << endl;
return ;
}
signed main(){
HelloWorld;
int tt = 1;
while(tt --) solve();
return 0;
}
题目五:https://www.luogu.com.cn/problem/SP1716
求最大字段和,太经典的题目了
如果查询区间的有边界小于等于
,说明查询区间在
的左子树;如果查询区间的左边界大于
,说明查询区间在
的右子树,直接返回即可
if(r <= mid) return query(u << 1, l, r); if(l > mid) return query(u << 1 | 1, l, r);如果都不是的话,说明查询区间横跨左右子树,所以得新建一个
Node left = query(u << 1, l, r); Node right = query(u << 1 | 1, l, r); Node res;
总代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N = 500000 + 100;
int n, m, a[N];
struct Node{
int l, r;
int sum, lmax, rmax, tmax;
}tr[4 * N];
void push_up(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
tr[u].lmax = max(tr[u << 1].lmax, tr[u << 1].sum + tr[u << 1 | 1].lmax);
tr[u].rmax = max(tr[u << 1 | 1].rmax, tr[u << 1 | 1].sum + tr[u << 1].rmax);
tr[u].tmax = max({tr[u << 1].tmax, tr[u << 1 | 1].tmax, tr[u << 1].rmax + tr[u << 1 | 1].lmax});
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, l, a[l], a[l], a[l], a[l]};
return ;
}
else{
tr[u] = {l, r, 0, 0, 0, 0};
int mid = (tr[u].l + tr[u].r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
push_up(u);
}
}
void modify(int u, int pos, int x){
if(tr[u].l == tr[u].r){
tr[u].sum = x;
tr[u].lmax = x;
tr[u].rmax = x;
tr[u].tmax = x;
return ;
}
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(pos <= mid) modify(u << 1, pos, x);
else modify(u << 1 | 1, pos, x);
push_up(u);
}
}
Node query(int u, int l, int r){
if(l <= tr[u].l && tr[u].r <= r) return tr[u];
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(l > mid) return query(u << 1 | 1, l, r);
if(r <= mid) return query(u << 1, l, r);
Node left = query(u << 1, l, r);
Node right = query(u << 1 | 1, l, r);
Node res;
res.sum = left.sum + right.sum;
res.lmax = max(left.lmax, left.sum + right.lmax);
res.rmax = max(right.rmax, right.sum + left.rmax);
res.tmax = max({left.tmax, right.tmax, left.rmax + right.lmax});
return res;
}
}
signed main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> a[i];
build(1, 1, n);
while(m --){
int op; cin >> op;
if(op == 1){
int x, y; cin >> x >> y;
if(x > y) swap(x, y);
cout << query(1, x, y).tmax << endl;
}
else{
int x, y; cin >> x >> y;
modify(1, x, y);
}
}
return 0;
}
题目六:https://www.luogu.com.cn/problem/P10463
求带修改操作的区间
,太经典的题目了
因为
,所以维护普通的区间
区间修改操作,维护差分进行两次单点修改
总代码:
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define HelloWorld IOS;
const int N = 500000 + 100;
int n, m, a[N];
struct Node{
int l, r;
int sum, Gcd;
}tr[4 * N];
int gcd(int a, int b){
if(!b) return a;
else return gcd(b, a % b);
}
void push_up(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
tr[u].Gcd = gcd(tr[u << 1].Gcd, tr[u << 1 | 1].Gcd);
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, a[l] - a[l - 1], a[l] - a[l - 1]};
return ;
}
else{
tr[u] = {l, r, 0, 0};
int mid = (tr[u].l + tr[u].r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
push_up(u);
}
}
void modify(int u, int pos, int x){
if(tr[u].l == tr[u].r){
tr[u].sum += x;
tr[u].Gcd += x;
return ;
}
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(pos <= mid) modify(u << 1, pos, x);
else modify(u << 1 | 1, pos, x);
push_up(u);
}
}
int query_sum(int u, int l, int r){
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
else{
int mid = (tr[u].l + tr[u].r) >> 1;
int sum = 0;
if(l <= mid) sum += query_sum(u << 1, l, r);
if(r > mid) sum += query_sum(u << 1 | 1, l, r);
return sum;
}
}
int query_gcd(int u, int l, int r){
if(l <= tr[u].l && tr[u].r <= r) return tr[u].Gcd;
else{
int mid = (tr[u].l + tr[u].r) >> 1;
int g = 0;
if(l <= mid) g = gcd(query_gcd(u << 1, l, r), g);
if(r > mid) g = gcd(query_gcd(u << 1 | 1, l, r), g);
return g;
}
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> a[i];
build(1, 1, n);
while(m --){
char op; cin >> op;
if(op == 'Q'){
int l, r; cin >> l >> r;
int x = query_sum(1, 1, l);
int y = query_gcd(1, l + 1, r);
cout << abs(gcd(x, y)) << endl;
}
else{
int l, r, x; cin >> l >> r >> x;
modify(1, l, x);
if(r + 1 <= n) modify(1, r + 1, -x);
}
}
return ;
}
signed main(){
HelloWorld;
int tt = 1;
while(tt --) solve();
return 0;
}
扫描线求并面积,相当于线段树扫描线的经典模板题目
扫描线:试想,如果我们用一条竖直直线从左到右扫过整个过程,那么直线上被并集图形覆盖的长度只会在每个矩形的左右边界发生变化。换言之,整个并集图形可以被分成
段,每一段在直线上覆盖的长度(记为
)是固定的,因此该段的面积就是
该段的宽度,各段面积之和即为所求。这条直线就被称为扫描线,这种解题思路被称为扫描线法。
struct segment{
int x, y1, y2;
int k;
}seg[N];
叶子节点
表示的线段树
区间
维护离散化后的区间
struct Node{
int l, r;
int cnt;
int len;
}tr[4 * N];
用于离散化纵坐标
vector<int> v;
将所有矩形的线段按照
从小到大排序,按照扫描线从左往右扫描整个平面
bool cmp(segment a, segment b){
return a.x < b.x;
}
二分离散化查找离散化后的数组下标
int find(int y){
return lower_bound(v.begin(), v.end(), y) - v.begin();
}
在
函数中,如果
,说明该区间被覆盖,那么该区间的
就设置为
如果
,在如果
不是叶子节点,即
,则
等于左右两个子区间的
如果
并且也是叶子节点,则区间长度设置为
void push_up(int u){
if(tr[u].cnt) tr[u].len = (v[tr[u].r + 1] - v[tr[u].l]);
else if(tr[u].l != tr[u].r) tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
else tr[u].len = 0;
}
在
函数中,
函数的作用是修改区间的
,从而通过
影响区间的
在判断语句
中,多了一步
操作 当前
的
有变化,则也会影响当前
的l
,所以也会
void modify(int u, int l, int r, int k){
if(l <= tr[u].l && tr[u].r <= r){
tr[u].cnt += k;
push_up(u);
return ;
}
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) modify(u << 1, l, r, k);
if(r > mid) modify(u << 1 | 1, l, r, k);
push_up(u);
}
}
离散化后的纵坐标有
个,那么有
个区间,因为下标从
开始,所以最后一个区间范围是
,所以线段树维护区间范围是
build(1, 0, v.size() - 2);
累加答案时,直接调用这个区间的长度
注意
要减去
,因为
包含的是下一个区间,所以得减去
for(int i = 0; i < idx; i ++){
if(i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x);
modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
}
总代码:
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define HelloWorld IOS;
const int N = 200000 + 100;
struct segment{
int x, y1, y2, k;
}seg[N];
struct Node{
int l, r;
int cnt, len;
}tr[4 * N];
vector<int> v;
int find(int y){
return lower_bound(v.begin(), v.end(), y) - v.begin();
}
bool cmp(segment a, segment b){
return a.x < b.x;
}
void push_up(int u){
if(tr[u].cnt) tr[u].len = v[tr[u].r + 1] - v[tr[u].l];
else if(tr[u].l != tr[u].r) tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
else tr[u].len = 0;
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, 0, 0};
return ;
}
else{
tr[u] = {l, r, 0, 0};
int mid = (tr[u].l + tr[u].r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int l, int r, int k){
if(l <= tr[u].l && tr[u].r <= r){
tr[u].cnt += k;
push_up(u);
return ;
}
else{
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) modify(u << 1, l, r, k);
if(r > mid) modify(u << 1 | 1, l, r, k);
push_up(u);
}
}
void solve(){
int n; cin >> n;
int idx = 0;
for(int i = 0; i < n; i ++){
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
seg[idx ++] = {x1, y1, y2, 1};
seg[idx ++] = {x2, y1, y2, -1};
v.push_back(y1), v.push_back(y2);
}
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
build(1, 0, (int)v.size() - 2);
sort(seg, seg + idx, cmp);
int res = 0;
for(int i = 0; i < idx; i ++){
if(i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x);
modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
}
cout << res << endl;
return ;
}
signed main(){
HelloWorld;
int tt = 1;
while(tt --) solve();
return 0;
}
后面再更新



京公网安备 11010502036488号