校门外的树
前言:
因为是第一次了解到前缀和和差分,刚开始做校门外的树这题时,没有用到,直接用的排序,到值周时,因为数据问题,去搜索了有关前缀和的有关方面。
关键点:
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;
}