加载中...

区间题:区间的单调性 离散差分 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;
}
posted @ 2022-02-07 23:35  liang302  阅读(56)  评论(0编辑  收藏  举报