HDU 5358 First One(尺取法)

First One

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)


Problem Description
soda has an integer array  a1,a2,,an . Let  S(i,j)  be the sum of  ai,ai+1,,aj . Now soda wants to know the value below:
i=1nj=in(log2S(i,j)+1)×(i+j)

Note: In this problem, you can consider  log20  as 0.
 

Input
There are multiple test cases. The first line of input contains an integer  T , indicating the number of test cases. For each test case:

The first line contains an integer  n   (1n105) , the number of integers in the array.
The next line contains  n  integers  a1,a2,,an   (0ai105) .
 

Output
For each test case, output the value.
 

Sample Input
   
   
   
   
1 2 1 1
 

Sample Output
   
   
   
   
12
 

Source
2015 Multi-University Training Contest 6
 
/***************************************************************************/

题意:给你一个整数序列a1,a2,a3,…,an,要求求出


的值,S(i,j)表示ai+ai+1+ai+2+…+aj

解题报告:观察式子可以知道

的值是x的二进制表示的位数。

比如x=5,它的二进制表示是101,将x=5代入上式,可得3,正是101的位数

另外题目又规定n的大小以及ai的大小都是100000以内的,所以,二进制位数最多34位,即

又因为该题的时间复杂度至多为O(nlogn),所以我们只需枚举位数k,再利用尺取法求出对应的i、j值即可。尺取法的时间复杂度是O(n)的。不知道尺取法的可以参考一下网址“算法初步——尺取法“,最好下载来看,毕竟ppt里有动画,看着比较直观。本来想要弄个学习笔记的,但是貌似CSDN不支持动画。

每对应一个i值,利用尺取法求出满足条件的j值的范围(a<=j<=b),则

i+j=(i+a)+(i+a+1)+(i+a+2)+…+(i+b)=(b-a+1)*i+(a+b)*(b-a+1)/2

换句话说,枚举位数i:1~35。对于每一位i找到区间[x,y],使得S(x,y)的二进制表示的位数等于i,此时的值为i*(x+y)。那么对于每一个i,怎么找出所有符合条件的区间[x,y]?1~n枚举起点x,那么y会在一段范围[l,r]内满足条件。下次x变成x+1,即起点x向右移位,那么现在要找的y的区间为[l',r'],l'不至于小于l同样r'不至于小于r。这样可以用O(n)的复杂度找出所有区间使得区间和的二进制表示的位数为枚举的i。

以下是AC代码 有什么不懂的地方可以提出来 

需要指出的一点是该方法仅用G++提交才能AC,若C++仍为TLE,据说是因为G++的输入输出比较快

#include<stdio.h>
const int N = 100005;
__int64 l[50],r[50],s[N],ans,a,b,num;
int main()
{
    int t,i,j,n,x;
	for(i=1;i<34;i++)
	{
		l[i]=(1ll<<i);
		r[i]=((1ll<<(i+1))-1);
	}
	l[0]=0;r[0]=1;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(i=1;i<=n;i++)
		{
			scanf("%d",&x);
			s[i]=s[i-1]+x;//求出前i项的和,则s[j]-s[i-1]即为下标i到j部分的总和
		}
		ans=0;
		for(i=1;i<35;i++)//枚举位数
		{
			if(s[n]<l[i-1])
				break;
			a=1,b=num=0;
			for(j=1;j<=n;j++)
			{
				a=(a>j?a:j);
				while(a<=n&&s[a]-s[j-1]<l[i-1])//找出满足当前位数的总和的最小下标
					a++;
				b=(b>a-1?b:a-1);
				while(b+1<=n&&s[b+1]-s[j-1]>=l[i-1]&&s[b+1]-s[j-1]<=r[i-1])//找出满足当前位数的总和的最大下标
					b++;
				if(b>=a)
					num+=(b-a+1)*j+(b+a)*(b-a+1)/2;//(j+a)+(j+a+1)+(j+a+2)+…+(j+b)=(b-a+1)*j+(a+b)*(b-a+1)/2
			}
			ans+=num*i;
		}
		printf("%I64d\n",ans);
	}
	return 0;
}

欢迎大家指点

菜鸟成长记

你可能感兴趣的:(算法,ACM,尺取法)