双指针算法,位运算,离散化和区间合并

双指针算法:强调单调性

先上模版(最长连续不重复子序列)

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

const int s = 1e6 + 10;
int j[s], k[s];     //j是原数组,k数组是存储各种数出现的次数
int n, ans;
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 0; i < n; i++) cin >> j[i];
    for (int r = 0, l = 0; r < n; r++)     //设置了l r左右指针
    {
        k[j[r]]++;     //r每向右移,l,r围成的区间就多包一个数,这个数的次数要加一
        while (l < r && k[j[r]]>1)      //l要向右移动至该数出现的次数小于等于1
        {
            k[j[l]]--;     //l每右移一位,区间就少了一个数,减去该数次数
            l++;
        }
        ans = max(ans, r - l + 1);     //取最大值为答案
    }
    cout << ans;
}

单调性:每次r向右移,l只可能往右移。易证

另外一个例子:

给定两个升序排序的有序数组 A和 B,以及一个目标值 x。

数组下标从 00 开始。

请你求出满足 A[i]+B[j]=x 的数对 (i,j)

数据保证有唯一解。

const int s = 1e5 + 10;
int n, m, x;
int j[s], k[s];
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m >> x;
    for (int i = 0; i < n; i++)
    {
        cin >> j[i];
    }
    for (int i = 0; i < m; i++)
    {
        cin >> k[i];
    }
    for (int dd = 0, ss = m - 1; dd < n; dd++)     //两指针一个指头,一个指尾
    {
        while (j[dd] + k[ss] > x) ss--;     //大了,ss指针往前走
        if (j[dd] + k[ss] == x)
        {
            cout << dd << ' ' << ss; break;
        }
    }
}

单调性:当dd往右移即就j【dd】增大时,ss只可能不动或向左移。

摩多摩多:

给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm

请你判断 a 序列是否为 b序列的子序列。

子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}的一个子序列。

const int N = 1e5 + 10;
int a[N], b[N];
int n ,m;

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int i = 0; i < m; i++) cin >> b[i];

    for(int i = 0, j = 0; i < n; i++)
    {
        while(j < m && a[i] != b[j]) j++;
        if(j == m) 
        {
            puts("No");
            return 0;
        }
        j++;//j需要移动到下一位才能够和i++匹配
    }
    puts("Yes");
    return 0;
}
单调性:i指针每往右移动,j也一定往右移。

总之,双指针是一种思想,在多处都可以用到(如快排和归并排序),两指针的行动要有相互关系,注意题目中的单调性

位运算

这里只讲一点

给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 11 的个数。

int n, ans;
int lowbit(int x)
{
    return (x & -x);
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    while (n--)
    {
        int x; cin >> x;
        ans = 0;
        while (x)
        {
            x -= lowbit(x);
            ans++;
        }
        cout << ans << ' ';
    }
}

lowbit函数返回的是一个数的二进制形式,从右往左的第一个1所带的序列。如x=1101000,返回的值是1000。

实现方法:x&(~x+1),~是反码,而~x+1刚好是-x(即x的补码)

如x=1101000,~x=0010111,~x+1=0011000(可以发现原数x从右往左第一个1的位置在~x+1中变成1)x&(~x+1)=0001000=1000

离散化

将稀疏的数利用一一映射的方式集中,减少占用空间,在数分布的范围广,数却不多稀疏的情况下很有用

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。

现在,我们首先进行 n 次操作,每次操作将某一位置 x上的数加 c。

接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。

原题x、l、r的范围很大,但n m较小


const int s = 300010;
vector<int> alls;     //将放入的值映射为该数组下标
typedef pair<int, int> p;     //用pair打包x和v,l和r
vector<p>j, k;
int a[s], b[s];
int n, m;
int find(int x)     //作用是找到数的映射对应的数值,用二分
{
    int l = 0, r = alls.size() - 1;
    int mid;
    while (l < r)
    {
        mid = (l + r) >> 1;
        if (alls[mid] == x) { r = mid; break; }
        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, v;
        cin >> x >> v;
        j.push_back({ x,v });
        alls.push_back(x);     //@
    }
    for (int i = 0; i < m; i++)
    {
        int l, r;
        cin >> l >> r;
        k.push_back({ l,r });
        alls.push_back(l);
        alls.push_back(r);     //和@一起都放入,坐标上的都要映射(加的值v就不用)
    }
    sort(alls.begin(), alls.end());     //要先排序,映射不能改变顺序(sort优先用pair第一个数排序)
    alls.erase(unique(alls.begin(), alls.end()), alls.end());     //unique函数去重复的数
    for (auto i : j)
    {
        int lx = find(i.first);     //find函数找映射值
        a[lx] += i.second;
    }
    for (int i = 1; i <= alls.size(); i++) b[i] = b[i - 1] + a[i];     //创建前缀和数组
    for (auto i : k)
    {
        int dl = find(i.first), dr = find(i.second);
        int ans = b[dr] - b[dl - 1];     //使用前缀和数组
        cout << ans << endl;
    }
}

注意点:离散化不改变数组原来的大小顺序,因此这题使用很合适。

区间合并

用了贪心的思想,将各区间的最左端进行排序后向右讨论

给定 n个区间 [l,r],要求合并所有有交集的区间。

注意如果在端点处相交,也算有交集。

输出合并完成后的区间个数。

例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。

typedef pair<int, int> p;     //起末用pair打包
vector<p> j, ans;     
int n;
vector<p> merge(vector<p>& j)
{
    vector<p>tmp;
    int st = -2e9, ed = -2e9;     //设定为无穷左的数,使第一次判断一定成立,
    sort(j.begin(), j.end());
    for (auto i : j)
    {
        if (ed < i.first)     //如果不相接就新开区间
        {
            if (st != -2e9) tmp.push_back({ st,ed });     //加判断,防止初始的区间加入
            st = i.first, ed = i.second;
        }
        else ed = max(ed, i.second);     //如果相接,就取最长尾,更新现有区间
    }
    if (st != -2e9) tmp.push_back({ st,ed });     //单独放最后的区间,加判断不要放初始区间
    return tmp;
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int l, r;
        cin >> l >> r;
        j.push_back({ l,r });
    }
    ans = merge(j);
    cout << ans.size();
}
 

如果(肯定有,有不妥之处请指出,看顺眼的话请点个赞吧~~~

持续更新中~~~

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值