区间题:区间的单调性 离散差分 int 10^6 =4m 区间合并 区间分段 区间交(想到贪心)
遇到 区间单调性而且是求和 必须想到前缀和
遇到 在某个区间【左闭右闭】各个数进行一个操作 最后求操作后区间总和 必须想到差分
离散化 就是先存点 再哈希找没存过的点 存差分 再求前缀和
遇到各个区间 需要知道找一个点重合的部分==不重复的部分 PII 右端点
分组获得不重复各个区间 使用小根堆 存每个组最右值
区间覆盖 找到若干个区间连接起来可以把区间覆盖 遍历每个区间 找到包含st的区间 当前区间的最右端变成st的右值
遇到求两个区间交集的个数 a-b c-d 那么就需要 min(b,d)-max(a,b);
截断数组(哈希表)
查询左边和是不是存在就可以
对一个数字大于0的n个数组截断三个区间 使得第一个区间和第三个区间
LL s[N]
signed main(){
cin>>n;
for(int i=0;i<n;i++){
int x;
cin>>x;
s[i]=s[i-1]+x;//读入每一个数同时计算前缀和
}
unordered_set<LL> hash;//
hash.insert(s[1]);
int res=0;
for(int i=2;i<=n;i++){//指针二计算某个区间的值,i是左边界
LL s3=s[n]-s[i-1];//s3表示右边区域的总和
if(hah.count(s3)){//存在输出
cout<<s3;return 0;
}
hash.insert(s3);//存放结果
}
return 0;
}
递增三元组 给出三个数组 满足a[i]<b[j]<c[k]
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int a[N],b[N],c[N];
long long res=0;
int n;
int find1(int x) // 并查集
{
int l=1,r=n;
// cout << "l=="<<l<<" 1r=="<<r<<endl;
while(l<r){
int mid =l+r+1>>1;
if(a[mid]<x) l=mid;
else r=mid-1;
}
// cout << "l=="<<l<<" r=="<<r<<endl;
return l;
}
int find2(int x){
int l=1,r=n;
while(l<r){
int mid =l+r>>1;
if(c[mid]>x) r=mid;
else l=mid+1;
}
return r;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) cin >> b[i];
for (int i = 1; i <= n; i ++ ) cin >> c[i];
sort(a+1,a+1+n);
sort(b+1,b+1+n);
sort(c+1,c+1+n);
int pa,pc;
for (int i = 1; i <= n; i ++ ){
pa=find1(b[i]);
// cout << pa<<endl;
// cout<<pa<<endl;
pc=find2(b[i]);
// cout<<pc<<endl;
if(a[pa] <b[i] && c[pc]>b[i])
res+=(long long )pa*(n+1-pc);
}
cout << res;
return 0;
}
彗星统计 统计前缀和的时候需要充分考虑可能开到的区间 不同拆分操作两次前缀和
链接:https://ac.nowcoder.com/acm/contest/23479/C
来源:牛客网
根据预测,有两种不同颜色的彗星:红彗星和蓝彗星。每颗彗星会在某一时间段出现 t 秒然后消失。
小红想知道,自己总共有多少秒,能看到蓝彗星且看不到红彗星?
输入
链接:https://ac.nowcoder.com/acm/contest/23479/C
来源:牛客网
第一行输入两个正整数 n 和 t ,用空格隔开。分别代表彗星的数量、每个彗星的持续时间。
第三行输入一个长度为 n的,只有两种字符'B'和'R'组成的字符串。用来表示每颗彗星的颜色。字符'B'代表蓝色,字符'R'代表红色。
第三行输入 n 个正整数 a
输出能看到蓝彗星且看不到红彗星的总秒数。
#include<bits/stdc++.h>
using namespace std;
int n,k;
string s;
int sum1[202020],sum2[202020];
int main(){
int i,x;
cin>>n>>k;
string s;
cin>>s;
for(i=0;i<n;i++){
cin>>x;
if(s[i]=='B')sum1[x]++,sum1[x+k]--;//和模板不同,需要将两个差分+ 和 -操作拆开来
else sum2[x]++,sum2[x+k]--;
}
int res=0;
for(i=1;i<=2e5;i++)//这里统计前缀和的时候需要充分考虑可能开到的区间
sum1[i]+=sum1[i-1],sum2[i]+=sum2[i-1],res+=(sum1[i]&&!sum2[i]);//通过&&!使得操作改变
cout<<res;
}
https://www.acwing.com/problem/content/1717/
给出若干个区间 加上同一个数 求在总数轴上哪个区间的数最大 满足差分
注意的是 加的是点 统计的是区间 如统计[2,5] 计算区间是3个 2,3 3,4, 4,5 加的时候也得+3个 为了改变 将加的点变成加区间 [l,r-1 ]
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e3+10;
int a[N],sum[N];
int main()
{
int n,res=0;cin>>n;
int s,t ,b;
for (int i = 0; i < n; i ++ ){
cin>>s>>t>>b;
a[s]+=b,a[t]-=b;
}
for (int i = 1; i <=1000; i ++ ){
sum[i]=a[i]+sum[i-1];
res=max(res,sum[i]);
}
cout << res;
return 0;
}
粉刷栅栏https://www.acwing.com/problem/content/description/1989/
数据范围在1e9 不可以用普通差分
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
#define x first
#define y second
const int N = 1e5+10;
int n;
map<int ,int >b;//使用mp存放差分
int main()
{
int n,x=0;
cin>>n;
for (int i = 0; i < n; i ++ ){
int y;char s;
cin>>y>>s;
if(s=='R') b[x]++,b[x+y]--,x+=y;//一般题意x+y是因为加在区间上 而不是点上
else b[x-y]++,b[x]--,x-=y;
}
int res=0,sum=0,last;//last是重点
for(auto &[x,v ] :b ) {//map离散化
if(sum>=2 ) res+=(x-last);
sum+=v;
last=x;
}
cout<<res;
return 0;
}
棒球女郎https://www.acwing.com/activity/content/problem/content/6514/ 离散差分
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int inf =2e9;
int n,x,y,z;
map<int ,int >b;
int main()
{
cin>>n>>x>>y>>z;
for (int i = 0; i < n; i ++ ){
int l,r;cin>>l>>r;
b[-inf]+=x;
b[l]+=y-x;
b[r+1]+=z-y;
b[inf]-=z;
}
int res=0,sum=0;
for (auto &[x, y] :b){
sum+=y;//前缀和
res=max(res,sum);
}
cout << res;
return 0;
}
手写离散
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 20010, INF = 2e9;
int n, x, y, z;
vector<int> xs;
int l[N], r[N], b[N * 2];
int find(int v)//再存放端点xs数组中找到端点并返回下标 xs是紧密的贴在一起的
{
int l = 0, r = xs.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (xs[mid] >= v) r = mid;
else l = mid + 1;
}
return r;
}
int main()
{
scanf("%d%d%d%d", &n, &x, &y, &z);
xs.push_back(-INF), xs.push_back(INF);//先离散化再映射
for (int i = 0; i < n; i ++ )
{
scanf("%d%d", &l[i], &r[i]);
xs.push_back(l[i]);
xs.push_back(r[i] + 1);//vector 的xs用来存放端点
}
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());//映射之前先去重复 unique返回去重后数组后一个
for (int i = 0; i < n; i ++ )
{
int L = find(l[i]), R = find(r[i] + 1);//从xs数组中找出下标 在xs L和R 用这个下标进行区间差分
b[0] += x;
b[L] += y - x;
b[R] += z - y;
b[xs.size() - 1] -= z;
}
int res = 0, sum = 0;
for (int i = 0; i < xs.size(); i ++ )
{
sum += b[i];
res = max(res, sum);
}
printf("%d\n", res);
return 0;
}
救生员 https://www.acwing.com/problem/content/1752/
接下来 N 行,每行描述一个救生员的工作班次,包含两个整数,表示一个救生员的开始工作时刻和结束工作时刻。
所有时刻各不相同,不同救生员的工作班次可能有覆盖。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define y second
#define x first
typedef pair<int,int> PII ;
const int N = 110;
PII q[N];
int main()
{
int n;cin>>n;
for (int i = 1; i <= n; i ++ )
cin>>q[i].x>>q[i].y;
sort(q+1,q+1+n);
int res=0;
for (int i = 1; i <= n; i ++ ){//因为说了每次删除一些点 枚举每一个点的情况
int sum=0,st=-1,ed=-1;
for (int j = 1; j <= n; j ++ )
if(i!=j) {
if(q[j].x<=ed ) ed=max(ed,q[j].y);//下一段的起点还在上一段的范围内
else {
sum+=ed-st;//分开的下一段开始了 就加上总和
ed=q[j].y ; //下一段 的 x y
st=q[j].x;
}
}
sum+=ed-st;//最后一段需要补上
res=max(res,sum);
}
cout << res;
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N], b[N]; // 前缀和数组和差分数组
int l[N], r[N]; // 存储每个救生员工作时间的左右端点
int main()
{
cin >> n;
for (int i = 0; i < n; i ++) {
cin >> l[i] >> r[i];
b[l[i]] ++, b[r[i]] --; // 差分
}
int res = 0;
for (int i = 0; i < n; i ++) {
int cnt = 0;
b[l[i]] --, b[r[i]] ++; // 去掉该救生员
for (int i = 0; i < N; i ++) {
a[i + 1] = a[i] + b[i]; // 计算前缀和
if (a[i + 1] > 0) cnt ++; // 统计覆盖数
}
res = max(res, cnt); // 更新最大结果
b[l[i]] ++, b[r[i]] --; // 该方案完成,恢复现场
}
cout << res;
return 0;
}
截断数列 https://www.acwing.com/problem/content/description/4304/
将数字数列截成n段 每段数字总和相等
数据范围小 枚举n^3
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
char d[N];
int main()
{
cin >> n >> d;
int sum = 0;
for (int i = 0; i < n; i ++ )
{
d[i] -= '0';
sum += d[i];
}
for (int k = 2; k <= n; k ++ )//想分成n段 因为区间都是整数 那么区间一定能被整除
if (sum % k == 0)//能被整除
{
bool flag = true;
int s = sum / k;//确定区间和
for (int j = 0, t = 0; j < n; j ++ )
{
t += d[j];//t为暂时的区间和
if (t > s)//如果不能恰好相等 那么必然会有问题
{
flag = false;//这个区间不可以
break;
}
else if (t == s)//确定可以
{
t = 0;//t设置为0 重新开始
}
}
if (flag)如果可以 输出
{
puts("YES");
return 0;
}
}
puts("NO");
return 0;
}
区间和 离散化
include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N];
vector<int> alls;//all存入所有需要操作的点 让这些点离散化
vector<PII> add, query;
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ )
{
int x, c;
cin >> x >> c;
add.push_back({x, c});
alls.push_back(x);
}
for (int i = 0; i < m; i ++ )
{
int l, r;
cin >> l >> r;
query.push_back({l, r});
alls.push_back(l);
alls.push_back(r);
}
// 去重
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
//酱紫all里面的点全部都是需要操作的点
//-1000 0 45 68
下标
// 0 1 2 3
// 处理插入
for (auto item : add)
{
int x = find(item.first);//题意在某一个位置上加上c
a[x] += item.second;
}
// 预处理前缀和
for (int i = 1; i <= alls.size(); i ++ ) s[i] = s[i - 1] + a[i];
//s是新建的前缀和数组 但和all一一对应 所以下面也可用find在all里面找到下标 然后处理在s里处理
// 处理询问
for (auto item : query)
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
使用stl map
map<x的位置,离散后的点 >
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, q;
int s[N];
PII a[N]; //记录哪个位置加了多少
PII b[N]; //记录询问的每一个区间
map<int, int>m; //使用map进行离散化,map默认从小到大
int main()
{
cin >> n >> q;
//先将每一个用到的坐标加到map里面
for(int i = 0; i < n; i++)
{
cin >> a[i].x >> a[i].y;
m[a[i].x] = 1;//暂时设定为1
}
for(int i = 0; i < q; i++)
{
cin >> b[i].x >> b[i].y;
m[b[i].x] = 1, m[b[i].y] = 1;
}
int idx = 0;
// map默认是从小到大的顺序
// map的second记录该数按照从小到大的顺序应该放在哪个位置
for(auto &t : m)
{
t.y = ++ idx;//关键 这里为所有的点设定了s的下标
}
// 使用前缀和
// 该数放的位置处加上要加的数
for(int i = 0; i < n; i++)
{
s[m[a[i].x]] += a[i].y;//加值离散后加到mp上
}
// 前缀和
for(int i = 1; i <= idx; i++) s[i] += s[i-1];//注意这里的idx
// 根据数字放在哪里进行求取(前缀和思想)
for(int i = 0; i < q; i++)
{
cout << s[m[b[i].y]] - s[m[b[i].x] - 1] << endl;
}
return 0;
}
连号区间数https://www.acwing.com/problem/content/1212/
两个指针 每次j进来就看看是不是最大值最小值
当 最大值-最小值 == i-j 说明满足一个区间(因为没连续的数)
const int N = 1e4+10;
int q[N];
int main()
{
int n;
cin >> n;
int res=0;
for (int i = 0; i < n; i ++ ) cin >> q[i];
for (int i = 0; i < n; i ++ ){
int minv=1e8,maxv=-1e8;
for (int j = i; j < n; j ++ ){
minv=min(minv,q[j]);
maxv=max(maxv,q[j]);
if(maxv-minv==(j-i)) res++;
}
}
cout << res;
return 0;
}
区间覆盖
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 1e5+10;
PII q[N];
int main()
{
int st,ed;
cin >> st>>ed;
int n;
cin >> n;
for (int i = 0; i < n; i ++ ){
int a,b;
cin >> a>>b;
q[i]={a,b};
}
int res=0;
bool success=false;
sort(q,q+n);
for (int i = 0; i < n; i ++ ){
cout<<"st"<<i<<"st="<<st<<endl;
int r=-2e9;
while( i<n&&q[i].x<= st ){//对所有左端小于线段上st的区间 找到最大值
r=max(r,q[i].y);
i++;//这里跳过i++在好用找小于这个st的时候好用但是 意味着没有处理对于下一个st来说(经过i++) 是少了一段的
}
if(r<st ) break;//r如果是-2e9说明找不到左端点从st开始的了
res++;
if(r>=ed){
success=true;
break;
}
cout<<"ed"<<i<<endl;
st=r;//注意st不是不变的 而是变成上一个区间的最小是
i--;//i是防止因为上面while里的i++ 少一个判断
}
if(success)
cout << res;
else cout << -1;
return 0;
}