http://codeforces.com/gym/101532/problem/A
题意
给定长度为n的数,要求求所有子区间内的数进行与运算(&)的和;
解题报告
对于一个数来说变成二进制的后可以变成二进制数位之和,例如:
7=120+1*21+12^2; 11=120+1*21+023+1*24;
因此根据与运算特性一旦出现了0&前面的数一定会变成0,要重新开始算,再讨论所有区间就完了。拿样例1来说:
a[1]=7,a[2]=9,a[3]=11可以看成
a[1]=0111
a[2]=1001
a[3]=1011
对于二进制第j位a[i][j-1],假设在区间(l,r)都是连续的1,因此这个区间内1的个数是(r-l+1)*(r-l+2)/2。
我们看看样例的第一个二进制位是111,区间是(1,3),它的子区间分别是(1,1)(1,2)(1,3)(2,2)(2,3)(3,3)也可以写成
(1,1)(2,1)(2,2)(3,1)(3,2)(3,3)(这里只是数的方向不同),因此公式就出来了。
下面代码(里面有个通过对数直接取最大二进制位数的优化,不知道为什么我用C++一定会超时,换了c直接a了)
代码
#include<iostream>
#include<stdio.h>
#include<bitset>
#include<cmath>
using namespace std;
const int maxn=1e5+5;
bitset<31>a[maxn];
int main()
{
int T;
while(scanf("%d",&T)!=EOF)
{
while(T--)
{
int n,temp;
int set=0;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&temp);
set=max(set,(int)log2(temp)+1);
a[i]=bitset<31>(temp);
}
long long ans=0;
a[n]=bitset<31>(0);
for(int i=0;i<set;i++)
{
long long rco=0;
int flag=0;
// cout<<"+++++++=flag="<<flag<<endl;
for(int j=0;j<n+1;j++)
{
if(flag==0)
{
if(a[j][i]==1)
{
rco++;
flag=1;
}
}
else
{
if(a[j][i]==1)
{
rco++;
}
else
{
flag=0;
ans+=(1<<i)*(long long)rco*(rco+1)/2;
rco=0;
}
}
// cout<<"j="<<j<<" "<<"flag="<<flag<<" "<<rco<<" "<<ans<<endl;
}
// cout<<"--------------------"<<endl;
}
cout<<ans<<endl;
}
}
}
Q.E.D.