刷题笔记(前缀和和差分)NC16649 校门外的树 NC24636 值周

校门外的树

值周

前言:

因为是第一次了解到前缀和和差分,刚开始做校门外的树这题时,没有用到,直接用的排序,到值周时,因为数据问题,去搜索了有关前缀和的有关方面。

关键点:

1、两题几乎一模一样,只有数据量的差别

校门外的树

先用结构体存数据,然后再根据起始点来排序,接下来开始遍历

代码:

# include <iostream>
# include <cstring>
# include <cmath>
# include <algorithm>
using namespace std;
int l, m;
int total;
int left=10000+10, rights = 0;
struct subway{
	int start;
	int end;
}s[100+10];
bool cmp(subway s1, subway s2)
{
	if (s1.start!=s2.start)
	return s1.start<s2.start;
	else
	return s1.end<s2.end;
}
int main()
{
	cin>>l>>m;
	total=l+1;
	for (int i=1; i<=m; i++)
	{
		int start, End;
		cin>>start>>End;
		s[i].start = start;
		s[i].end = End;
	}
	sort(s+1, s+1+m, cmp);
	total = total-(s[1].end-s[1].start+1);
	rights = s[1].end;
	for (int i=2; i<=m; i++)
	{
		if (s[i].start==rights)
		total = total-(s[i].end-s[i].start);
		else if (s[i].start>rights)
		total = total-(s[i].end-s[i].start+1);
		else if (s[i].start<rights&&s[i].end>rights)
		total = total-(s[i].end-rights);
		rights = max(rights, s[i].end);
	}
	cout<<total;
	return 0;
}

总的来说,还挺麻烦的,这里介绍其他解法

差分和前缀和

了解了前缀和和差分后,这里记录一下如何利用前缀和和差分思想

首先,我们可以算出每个区间被访问的次数,即每次该区间被遍历到,就将这个区间里的每个数加上1,这里就可以利用差分的思想来加1

定义数组delta[i]表示第i个数和i-1个数的差

那么当给出区间x,y

则将delta[x]++, delta[y+1]--

意思为第x数和第x-1个数相差1(x数比x-1数大),第y+1个数比第y个数小1,

完整代码:

# include <stdio.h>
int delta[100000000+10];
int l, m;
int cnt;
int main()
{
	scanf("%d%d", &l, &m);
	for (int i=1; i<=m; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		delta[x]++;
		delta[y+1]--;
	}
	int sum = 0;
	for (int i=0; i<=l; i++)
	{
		sum += delta[i];
		if (sum==0)
		cnt++;
	}
	printf("%d", cnt);
	return 0;
}

更新版的前缀和和差分

关键点:

首先我们发现第一版的做法,很明显浪费了大量的内存,我们想能不能直接对这几个m个区间进行操作求差分

我们开一个struct数组来记录差分的下标和相应的加1和减1

然后用一个sort函数来将这些差分排序,按照下标从小到大

struct ty{
	int pos;
	int num;
}delta[1000000+10];
bool cmp (ty t1, ty t2)
{
	if (t1.pos!=t2.pos)
	return t1.pos<t2.pos;
	else
	return t1.num<t2.num;
}

接下来,遍历这个结构体,用一个变量a来记当前的前缀和,我们想,

每次的+1和-1,最后都是在a(前缀和)从0变化到1这个范围内的树存在

	for (int i=1; i<=m*2; i++)
	{
		a += delta[i].num;
		if (a==1&&delta[i].num==1)
		cnt+=delta[i].pos-delta[i-1].pos;
	}

最后一个区间到结尾的树还没加上

	cnt+=(l-delta[m*2].pos+1);

完整代码:

# include <iostream>
# include <algorithm>
using namespace std; 
struct ty{
	int pos;
	int num;
}delta[1000000+10];
bool cmp (ty t1, ty t2)
{
	if (t1.pos!=t2.pos)
	return t1.pos<t2.pos;
	else
	return t1.num<t2.num;
}
int l, m;
int cnt;
int main()
{
	scanf("%d%d", &l, &m);
	for (int i=1; i<=m; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		delta[i].pos = x;
		delta[i].num = 1;
		delta[i+m].pos = y+1;
		delta[i+m].num = -1;
	}
	sort(delta+1, delta+1+m*2, cmp);
	int a = 0;
	for (int i=1; i<=m*2; i++)
	{
		a += delta[i].num;
		if (a==1&&delta[i].num==1)
		cnt+=delta[i].pos-delta[i-1].pos;
	}
	cnt+=(l-delta[m*2].pos+1);
	printf("%d\n", cnt);
	
	return 0;
} 

区间:

用一个结构体数组将每次区间的开头和结尾记下来,并且根据开头从大到小排序

struct ty{
	int s, e;
}qu[1000000+10];
bool cmp(ty t1, ty t2)
{
	if (t1.s!=t2.s)
	return t1.s<t2.s;
	else
	return t1.e<t2.e;
}

遍历该区间

采用合并区间的思想,当两个区间有重复的地方,我们可以合并,用一个r来记录当前区间的最长的结尾,当当前区间的头在这个l(最长的结尾)里时,那么两个区间就可以合并,但是我们无法确定当前的结尾是否会大于r,所以更新r

if (qu[i].s<r)
{
	r = max(qu[i].e, r);
}

一旦当前区间不发生重合,那么我们就可以减去上一个区间,并且更新新的区间(le,r)左右端点

完整遍历

for (int i=2; i<=m; i++)
	{
		if (qu[i].s<r)
		{
			r = max(qu[i].e, r);
		}
		else
		{
			cnt-=(r-le+1);
			le = qu[i].s;
			r = qu[i].e; 
		}
	}

最后还得算上最后一个区间

	cnt-=(r-le+1);

完整代码:

# include <iostream>
# include <algorithm>
using namespace std; 
int l, m;
struct ty{
	int s, e;
}qu[1000000+10];
bool cmp(ty t1, ty t2)
{
	if (t1.s!=t2.s)
	return t1.s<t2.s;
	else
	return t1.e<t2.e;
}
int main()
{
	scanf("%d%d", &l, &m);
	for (int i=1; i<=m; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		qu[i].s = x;
		qu[i].e = y;
	}
	sort(qu+1, qu+1+m, cmp);
	int cnt = l+1;
	int le = qu[1].s;
	int r = qu[1].e;
	for (int i=2; i<=m; i++)
	{
		if (qu[i].s<r)
		{
			r = max(qu[i].e, r);
		}
		else
		{
			cnt-=(r-le+1);
			le = qu[i].s;
			r = qu[i].e; 
		}
	}
	cnt-=(r-le+1);
	printf("%d", cnt);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值