去重排序,双指针,二分(例题进击的奶牛),位运算(MEX AND XOR)

去重排序:

一般去重排序用set容器,但是有时候想要用到vector的性质,因此掌握利用vector去重排序的方法是很有必要的:

vector去重排序的模板为:

sort(v.begin(),v.end());
 v.erase(unique(v.begin(),v.end()),v.end());

首先排序不必多说,是下面unique方法的前提。

去重分为unique和erase两次操作

unique方法传入两个迭代器,作用是把两个迭代器之间的重复的元素都挪到后面,它的返回值是第一个重复元素的迭代器,如下图所示:

unique把原向量中重复的1和2都挪到了后面,并且返回了指向1的迭代器。

接下来是erase方法,需要传入两个迭代器,作用是删除两个迭代器之间的元素,因此我们只需要把第一个重复的元素到最后一个元素之间的所有元素删除即可。

例题代码:
//1 1 2 2 4 3 6 5
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> v;
int main()
{
	for(int i=0;i<8;i++)
	{
		int x;
		cin>>x;
		v.push_back(x);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());//unique是把重复的数字放到后面,并且返回第一个重复的数字的下标
	for(auto i:v)
		cout<<i<<" "; 
}

 双指针:

双指针没有特定的模板,一般就是定义两个指针i和j,分别进行移动,直到指针之间的元素满足题目所要求的性质

双指针没有特定的模板,只能用下面一个例题加深一下理解:

题目:

最长连续不重复子序列
本题有t组样例,对于每组样例
给定一个长度为n的数组,求出其中最长的连续且无重复数字的子序列长度
输入
1
5
1 2 2 3 5
输出
3

输入
3
9
2 1 1 1 5 7 8 9 9
3
1 1 1
4
1 2 3 2
输出
5
1
3

解析

对于题目给出的数组,可以设置两个指针i和j,让j走的比i快。

j每次移动需要满足以下条件:

1.j没走到头

2.j+1指向的元素在从i到j的元素中没有出现过(因为题目要求无重复数字)

直到j走不动的时候,统计一下从i到j的长度,作为其中一个满足要求的子序列。至于长度是不是最长的,可以定义一个变量ans,每次j走不动时,进行ans = max(ans,j-i+1)操作更新最大值。然后再让i往前走一步,继续循环上述操作。

代码:
#include<iostream>
#include<string.h>
using namespace std;
const int N = 2e5+5;
int a[N],vis[N];
void solve()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		vis[a[i]] = 0;
	}
	int j=0,ans=0;
	for(int i=1;i<=n;i++)//指针i移动 
	{
		while(j<n&&!vis[a[j+1]]) vis[a[++j]]++;//指针j移动的条件:j+1没重复,并且没有走到头 
		ans = max(ans,j-i+1);
		vis[a[i]]--;
	}
	cout<<ans<<'\n';
}
int main()
{
	int t;
	cin>>t;
	while(t--)
		solve();
}

 二分:

二分的大致解题思路:

首先要确定能不能用二分做,如果能,需要对什么进行二分。一般题目要求什么就对什么二分,就比如要求数组中某个数字的下标,就对一定范围内的下标二分。

然后初始化l和r,一般比题目给出的需要进行二分的东西的取值范围多上一点防止极端情况。

接着就是写while循环,一般都是while(l+1!=r)

然后就是while循环内部的内容,先定义mid = (l+r)/2,然后进行判断,根据判断更新l或者更新r的值

最后循环结束后,l或者r就是所要的结果,具体是l还是r要看while循环中是怎么写的。

经典例题:进击的奶牛
题目:

牛棚n个隔间1e5,分布在一条直线上,坐标分别为x1..xn(0到1e9,闭区间)
有C(2<=C<=n)头奶牛因为牛棚里有其他的牛存在而愤怒
为了防止牛之间互相打斗,想把这些牛放在指定的隔间,使得所有牛中相邻的两头的最近距离越大越好
请问这个最大的最近距离是多少
第一行输入n和c
随后每一行输入每个隔间的坐标
输入
5 3
1
2
8
4
9
输出
3

解析:

对每头牛之间的最近距离进行二分,牛棚的坐标范围是0到1e9,取极端情况,可以每隔1就有一个牛棚,放一头牛,也可以整条直线就两端分别有一个牛棚,因此牛之间的最近距离范围是[1,1e9],稍微往外拓展一点防止意外,因此可以l=0,r=1e9+5。

我们不妨用逆向思维,假如知道了牛之间的最小距离,那么这些牛棚最多可以放多少头牛呢

为了求解这个问题,可以用到简单的贪心思想,把牛棚的坐标从小到大拍戏,第一个牛棚肯定是要放牛的,不然会浪费空间,然后遍历后面的牛棚,如果该牛棚和上一个放牛的牛棚的距离比我们能所知道的牛之间的最小距离小,那么这个牛棚可以放牛。就这样一直遍历到最后,就可以知道最多可以放多少头牛。

由常识可以知道,牛之间的最小距离越大,那么牛棚最多放的牛越少,当然这个关系不是线性的,而是随着牛之间的最小距离增大,突然有一个牛棚不能满足要求了,这样最多能放的牛就会少一个,所以他们之间的函数图像是如下图这样一段一段的横线:

其中max_c是牛棚最多能放多少头牛,min_dis是牛之间的最小距离。

如此一来,我们可以用二分找到当牛棚最多能放c(题目给出的牛的数目)头牛时所对应的牛之间的最小距离,当然这个距离很有可能时一段区间,这时候我们要取最大的。

对于代码的实现,不妨写一个函数f(x),传入的是牛之间最小距离,返回的是在该距离下最多能放多少头牛。

如果f(mid)>=C,说明mid比我们要求得距离小,这时候更新l的值,让l=mid。(这里需要用到<=,因为如上图所示,我们要找的是max_c = c那条线段的最右边,所以即使f(mid)=c,也有可能不是我们要求的最大距离)

反之就更新r的值。

最后l就是我们所求的结果。(为什么不是r呢,因为每次f(mid)<C时,才更新r的值,r=mid,因此r根本不可能取到f(r)=C)。

代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5+5;
int a[N];
int n,c;
int f(int x)//求假如两头牛之间的最小间隔为x,则最多能放多少头牛 ,贪心求解 
{
	int ans=0,pre = -1e9;
	for(int i=1;i<=n;i++)
	{
		if(a[i]-pre>=x)
		{
			pre = a[i];
			ans++;
		}
	}
	return ans;
}
int main()
{
	cin>>n>>c;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	sort(a+1,a+1+n);
	int l=0,r=1e9+5;//这里是对最小间隔进行的二分 
	while(l+1!=r)
	{
		int mid = (l+r)>>1;
		if(f(mid)>=c)//最小间隔为mid,放的牛比c多,说明最小间隔还能继续扩大,一定要大于等于c,因为可能好几个间隔所能放的牛都是c,我们要找最大的那一个 
			l = mid;
		else
			r = mid;
	}
	cout<<l;
}

位运算:

位运算的知识点比较简单也比较杂,本来不打算做笔记的,但有一道比较有价值的例题:

题目:

给出两个正整数a,b,可以写出一些非负整数的数组,使得该数组的MEX和XOR的值分别为a和b
你需要求出满足条件的非负整数数组的最小长度。
MEX值指的是最小的不存在该数组中的非负整数,如数组(0,3,2,2)的MEX值为1.
XOR值指的是数组中所有元素做异或运算的结果
第一行输入t,表示用例数量
随后t行,每行输入a,b [0,2e5]
输入:
5
1 1
2 1
2 0
1 10000
2 10000
输出:
3
2
3
2
3

解析:

MEX = a,所以我们所构造的数组里面必须有0到a-1,必须不能有a。

假设我们的数组由两部分构成,一部分是0,1,....,a-1另一部分是t,t是一个非负整数

我们要让0^1^...^a-1^t = b

根据异或的性质,我们可以移项,变成t = 0^1^...^a-1^b。

由于0到a-1和b我们都是知道的,可以直接算出来t

不过有两个特殊情况:

当t = 0时,由于0异或上任何数字都是那个数字本身,所以t可以省略不写,这时候我们所构造的数组就是[0,1,...,a-1],长度为a。

当t = a时,就不满足我们“最小的不存在于该数组中的非负整数为a的条件”,这时候,我们可以把t分解为x^y,这里涉及到异或的另外一个性质:任意一个数字t,都能分解成两个比他大的数字的异或,此时我们构造的数组为[0,1,...,a-1,x,y],长度为a+2。

其他情况,t可以直接放入我们所构造的数组里面,此时我们所构造的数组为[0,1,...,x-1,t],长度为a+1。

代码:
#include<iostream>
using namespace std;
const int N = 2e5+5;
int prefix[N];//前缀异或 
void solve()
{
	int a,b;
	cin>>a>>b;
	int t = prefix[a-1]^b;
	if(t==a)
		cout<<a+2<<'\n';
	else if(t==0)
		cout<<a<<'\n';
	else
		cout<<a+1<<'\n';
	
}
int main()
{
	int t;
	cin>>t;
	for(int i=1;i<N;i++)
		prefix[i] = prefix[i-1]^i;
	while(t--)
		solve();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

owooooow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值