题目链接:http://210.34.193.66:8080/vj/Contest.jsp?cid=160#P9
涨姿势之区间刷新
Value_Dragon是一个有钱人。快过年了,所以他准备发红包。但是他发红包的方式很奇葩。他让n个人排成一排。每次选择1-n中的一段区间[l,r]发,给区间中的每一个人一块钱。就这样发了m次红包。发完后他想知道在[1,n]的子区间中有多少个区间满足以下要求
-
这个区间得到钱的总数不少于s
-
这个区间可以被分成两个不相交的子区间且每个子区间得到的钱的总数不小于w
(注:一个区间的子区间包括自己本身)
防坑提醒,长度为1的区间比如[1,1],是不能被拆成两个子区间的
第一行是一个整数T代表数据的组数。
接下来有T组数据
每组数据开头有四个整数,分别代表n m s w
接下来m行,每行是是两个数l,r代表区间[l,r]的左右端点
其中T<=10
n<=10^6,m<=10^5
0<l<=r<=n
0<=w<=s<10^8
对于每组数据输出一行,代表符合要求的区间个数
4 1 0 0 0 1000000 0 0 0 1000000 1 0 0 1 1000000 10 10 20 14 2 10 5 9 5 5 6 8 2 6 9 10 6 7 6 10 4 5 5 7
0 499999500000 499999500000 8
思路:看到这题,我先想要怎么把发完压岁钱后的数据存下来,如果弄个数组,每次输入一个[l,r]就吧l到r数组里的数+1,这样的话万一输入的全是1 1000000这样的话输入一次就要做100万次加法再输入个10万次,这样的话就会计算1000亿次,肯定会超时一脸,这里需要一个小技巧,叫做差分,以下是差分代码
1 while (m--) 2 { 3 int x,y; 4 scanf("%d%d",&x,&y); 5 a[x]++,a[y+1]--; 6 } 7 for (int i=1;i<=n;++i) 8 a[i]=a[i-1]+a[i];
差分的意思大概是比如你输入1 3
它就会让a[1]++,a[4]--,然后区间刷新,让a[i]=a[i]+a[i-1],这样循环一次后,从a[1]到a[5]就会变成1,1,1,0,0,你们可以自己模拟一遍体会一下这种方法。
然后存好数据后就可以开始想办法解决问题了,因为要多次用到区间和,所以我用前缀和处理了一下数据
1 for (int i=1;i<=n;++i) 2 sum[i]=sum[i-1]+a[i];
然后我用尺取的方法先找到满足(这个区间得到钱的总数不少于s条件)的区间,然后再添加一个指针k,找到满足(这个区间可以被分成两个不相交的子区间且每个子区间得到的钱的总数不小于w)条件的分界点k,找到一个满足条件的区间后,可以一直增加到n都满足条件,所以答案每次会增加n-r+1。随着l的增加,k和r也只会单调增加(因为随着l增加,要满足条件1和条件2,[l,r]要大于s[l,k]要大于w),所以l,r,k都是单调增加的,时间复杂度为o(n)。以下是尺取代码。
1 ansx=a[1];///ansx代表当前尺取的区间和 2 for(l=1;l<=n&&l<=r;l++) 3 { 4 while(ansx<s&&r<n) 5 { 6 r++; 7 ansx+=sum[r]-sum[r-1]; 8 } 9 while(ansx>=s&&r<=n) 10 { 11 while(sum[k]-sum[l-1]<w&&k<n)k++; 12 if(sum[r]-sum[k]>=w&&l!=r) 13 { 14 ans+=n-r+1; 15 break; 16 } 17 else if(r==n)break; 18 else if(r<n) 19 { 20 r++; 21 ansx+=sum[r]-sum[r-1]; 22 } 23 } 24 ansx-=sum[l]-sum[l-1]; 25 }
答案有可能会爆int的表示范围,注意答案使用longlong,其他的用int就好了,longlong计算比int慢,有可能会导致超时。