先上题目:
10679 多少个1
时间限制:1000MS 内存限制:65535K
提交次数:288 通过次数:22
题型: 编程题 语言: C++;C
Description
Acm比赛里面经常需要巧妙方法处理数据,如求给定数组的最大连续和就可用到前缀和。现在给出一个给定长度的且初始化为0的数组, 然后对该数组的一系列区间做取反操作,最后询问数组中有多少个1。(取反操作:原来是0就变为1,是1就变为0)
输入格式
输入第一行是t表示case数。每个case的第一行是用空格隔开的正整数n和m(0<n<=10^18,0<=m<=100000)分别表示数组长度和总操作数, 接下来有m行,每行包含两个整数a和b(a、b之间用空格隔开,表示将位置a和位置b之间(包含a和b)的所有位进行取反操作,0<=a,b<n)。每个case后紧接一空行。
输出格式
每个case第一行输出:“Case #i:”,i表示第几个Case(从1开始);第二行输出数组1的个数
输入样例
2 2000000000 1 0 1000000000 1000 3 0 999 0 100 100 100
输出样例
Case #1: 1000000001 Case #2: 900
首先是最简单的方法:读入所有的区间,然后将区间里面的所有值取反。这样做绝对超时。
然后是深一层的方法:先对所有区间离散化,然后构造线段树,接着更新线段树的区间。思路上可行,没有用这种方法实现过,算了一下时间复杂度:
①排序+离散化 O(mlogm)
②建树 O(mlogm)
③更新区间 O(mlogm)
④最终求值O(mlogm)
这是上界,感觉不会超,但是实现起来还是比较麻烦。
然后说一下第三种方法:使用归一化思想,见过观察,我们可以发现更新一个区间[l,r]的操作等价于更新[l,+∞]然后再更新[r+1,+∞],所以我们可以先处理出所有的变换后的左区间,然后排一次序,从小到大排序,然后加减交替更新即可,因为更新奇数次为加,更新偶数次不变。总的时间复杂度为O(mlogm)
上代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <utility> 5 #include <vector> 6 #include <algorithm> 7 #define MAX 100002 8 #define ll long long 9 //#define lld I64d 10 using namespace std; 11 12 ll n,m; 13 ll l,r; 14 ll s[MAX<<1],tot; 15 16 int main() 17 { 18 int t; 19 //freopen("data.txt","r",stdin); 20 //ios::sync_with_stdio(false); 21 scanf("%d",&t); 22 for(int z=1;z<=t;z++){ 23 tot=0; 24 scanf("%lld %lld",&m,&n); 25 for(int i=0;i<n;i++){ 26 scanf("%lld %lld",&l,&r); 27 if(l>r) swap(l,r); 28 s[tot++]=l; 29 s[tot++]=r+1; 30 } 31 sort(s,s+tot); 32 ll sum=0,f=1; 33 for(int i=0;i<tot;i++){ 34 if(s[i]>m) break; 35 if(f){ 36 sum+=m-s[i]+1; 37 }else{ 38 sum-=m-s[i]+1; 39 } 40 f=f^1; 41 } 42 //cout<<"Case #"<<z<<":"<<endl<<sum<<endl; 43 printf("Case #%d:\n%lld\n",z,sum); 44 } 45 return 0; 46 }