双指针、BFS、图论

双指针、BFS、图论

双指针

两个for循环转化成一个for循环

image

例题:日志统计(在代码中i在前,j在后,相当于i在前面走,一直拽着j)

image

#include<iostream>
#include<algorithm>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
const int N=100010;
int n,d,k;
PII logs[N];
int cnt[N];
bool st[N];
int main()
{
	cin>>n>>d>>k;
	for(int i=0;i<n;i++) scanf("%d %d",&logs[i].x,&logs[i].y);
	sort(logs,logs+n);

	for(int i=0,j=0;i<n;i++)
	{
		int id=logs[i].y;
		cnt[id]++;
	
		while(logs[i].x-logs[j].x>=d)
		{
			cnt[logs[j].y]--;
			j++;
		}
		if(cnt[id]>=k)
		st[id]=true;
	}
	for(int i=0;i<=100000;i++)
	{
		if(st[i])
		cout<<i<<endl;
	}
	return 0;
}

BFS

基本框架

image

注意:bfs和dfs的区别

image

图论

1、由y总分析可知,如果我们当前位置的 i≠a[i]的话,那么它必然等于其他位置上的 a[i]

2、那么我们可以利用图的存储方式来描述我们整个数组

3、下面借用一下y总上课画的图
屏幕截图 2021-03-23 145121.png
4、我们连接的方式是把我们当前的位置的 a[i]连向我们本该在的位置的 a[i],(也可以理解为每 个i 都连向它的a[i],乱序的时候)那按这个规则连起来的话就会得到以上的联系规则

5、不难证明,如果把一个题目中所述的序列按照上述方法连接后,每一个连接都会是一个环,要么是自环,要么是与别人成环(因为总是会连回来的,因为每个位置的出度一定为 1 ,入度也一定为 1 ,所以必然成环 出度是指它所连向的点的数量,入度是指连向它的点的数量)

6、那么通过进一步分析可以知道,一个长度为 n 的序列做多会存在 n 个环,那就是当该序列为升序排列时,也就是题中所述的目标状态时

7、那么我们如果去操作这个方法呢?

8、其实我们不需要像的和图论题一样去真的去构造那么多环,我们只是利用这个思想,来模拟这个过程

9、下面讲一下具体的操作,引用一下y总上课画的图
屏幕截图 2021-03-23 145143.png

10、我们对于每一个环的操作,最多有两种情况,如上图所示,我们要么是在环内操作,要么就是跨环操作(环内操作就是在环内切换连接对象,环外操作就是在环与环之间切换连接对象)

11、其实上面的操作全都是一种思想,目的是为了引出一个结论,假设我们当前有 k 个环,但是我们最终情况是我们需要有 n 个环,所以我们至少需要操作 n−k 次(k显然小于n哈)

12、上面的所有描述都是为了证明,我们确实可以取到我们操作次数的最小值

13、那么我们有了这个结论那我们就可以……

14、等等,你是不是想骗大家,想糊过去是吧,你还没具体讲证明呢?具体操作呢??what?

15、好吧,其实我也不知道为什么这个是成立的,如果按 y 总所说的,只需要操作 n−k 次的话,那就意味着我们每次只能操作环内的情况,只能将一个环一分为二,每次操作必须增加一个环。

16、我怎么感觉也不太对呢……

17、可能是我没有仔细思考啊,我这里也是刚听完课所以把 y 总说的回顾了一遍,等我想通了我再来更新hh

18、讲一下代码具体怎么写,这个代码的时间复杂度是 O(n) 的,所以我们采取的方式还是从 1 枚举到 n

19、但是不同的是,我们为了保证我们只会把每个数枚举一遍,我们加了一个判重数组,来记录一下这个数是否被用过

20、我们在实际操作的时候呢,其实可以理解为一个“毁环的过程”,如果毁环呢?我们就把我们当前环内的点全部标记一下,那就相当于我们环内的点我们都用过了,不会再重复遍历,对于实际来说这个环就相当于与消失了。

21、我们每消灭一个环,我们就把计数器++,目的是为了记录一下我们一共有多少个环

22、最后利用我们的结论,我们用 n−k 那就是我们要的答案了

23、为什么我现在不把这个想清楚呢?是因为我马上要去上课了,等回来再补充

24、重点是:环其实不需要真实构造,我们可以利用代码简单的实现,这个会在代码里面注释,重要的是我们推出来的结论

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

const int N=10010;

int st[N];//判重数组

int a[N];

int n;

int main()
{
    scanf("%d",&n);

    for(int i=1;i<=n;i++)scanf("%d",&a[i]);


    int cnt=0;

    for(int i=1;i<=n;i++)
      if(!st[i])
      {
          cnt++;

          int t=i;

           st[t]=1;

          while(a[t]!=t&&a[t]!=i)//这里就是判断那个环了,先一直往下搜,搜到自己那就是完成了一个环了
          {
              t=a[t];

              st[t]=1;
          }
      }

      printf("%d",n-cnt);//重要的事情说三遍,重要的是结论!!!

      return 0;
}
  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值