题意
给你 n(1<=n<=10^5) 个数,每个数的范围是 [0, 10^6] ,定义一个函数 f(L, R) :把从第 L 个数取到第 R 个数全部取“或”得到的数,考虑所有的 L 和 R,问你能够得到多少个不同的 f(L, R)
做法分析
首先,没想法,但是我们知道这个结果一定不会超过 2^20,那么就先暴力试试吧:
1、将所有数存在数组 a 中
2、用 f[i][j] 表示从第 i 个到第 j 个,所有数“或”的值。其实我们可以在暴力枚举 j 的过程中再来暴力枚举 i ,这样就可以把二维数组化为一位数组了
3、定义一个数组 flag,flag[i] 表示 值为 i 的数是否是 f(L, R) 的一个结果
4、扫描 flag 数组,看哪些的值是 1,是就把答案加 1
5、输出答案
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
{
f[j]=f[j]|i;
flag[f[j]]=1;
}
很不幸,在第 13 组数据 T 了。。。
那该怎么优化,或者想其他的方法?
这里是对上面的优化
在上面的六行代码中,考虑到这样一个性质:第 2 行枚举 j 的时候其实是可以从 i-1 到 1 逆向枚举的,然后我们注意到 f[j] 表示的是什么:第 j 个元素到第 i 个元素之间所有元素“或”的值,为了方便,我们先把它用二维数组表示:f[j][i] , j<i 。仔细观察这样的一个过程:
我们已经知道了 f[j, i-1] ,现在新添加一个 a[i] 进来并且更新所有的 f[j, i]=f[j, i-1] | a[i] ,这就是上面暴力过程做的事情
注意这样一个事实:f[j, i]=f[j, k] | f[k+1, i] , 注意了,如果现在已经有了f[k+1, i]==f[k+1, i-1] ,那么对于所有的 1<=j<=k ,有 f[j, i] ==f[j, i-1] ,如果再把 f 数组化成一维数组,那么我们的所有 f[j] , 1<=j<=k 就不用改变,也就是说,在逆向循环 j 的时候,一旦我们遇到了 f[j]==f[j] | a[i] ,我们就可以 break 掉了,这有点单调队列的感觉,但不是很强烈,不过相信时间复杂度应该不是 n^2 的了,然后试着交了一发,78msAC
如果您还有其他的方法,欢迎回复给我,感激不尽!
AC传送通道
代码
#include <iostream>
#include <cstdio>
using namespace std;
int a, f[100005], fg[1<<20], n, cnt=0;
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &a);
for(int j=i-1; j>=1; j--)
if(f[j]!=(f[j]|a)) fg[f[j]=f[j]|a]=1;
else break;
fg[f[i]=a]=1;
}
for(int i=0; i<(1<<20); i++)
if(fg[i]) cnt++;
printf("%d\n", cnt);
}