单调栈 前缀和 异或7.21序列求和

Posted antiquality

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调栈 前缀和 异或7.21序列求和相关的知识,希望对你有一定的参考价值。

还要再细细思考的奇妙思路

题目描述

小A最近喜欢上了关于区间max的问题。她定义一个区间的价值是max(ai)(l<=i<=r)(alxoral+1xor...xorar)max(ai)(l<=i<=r)∗(alxoral+1xor...xorar)她想要知道,一个序列所有的连续子序列价值之和是多少。

输入格式

第一行一个正整数n接下来一行n个正整数表示aiai

输出格式

一个正整数表示答案,mod1000000007mod1000000007输出

样例输入

4

1 2 3 4

样例输出

103

数据规模与约定

对于前30%的数据,n<=1000

对于前60%的数据,满足n<=3000

对于前80%的数据,满足n<=50000

对于100%的数据,满足1<=n<=105,ai<=109


题目分析

常规思路

见到这题的时候真的是一脸不可做。求和涉及到区间max和区间xor的乘积,整个状态数本身就是$O(n^2)$的,即便写一个转移$O(1)$的暴力也只有60pts。

从问题的表面上来看,状态对答案贡献很难拆开统计。因为如果寻找贡献的共同之处,会发现只有$max(ai)$的地方能够共用。但是还有xor的部分,依然有$O(n^2)$不尽相同的状态。

按位拆分

不过实际上只有我这种不老练选手觉得无从下手

引用HZQ的话:“碰到位运算+求和的问题一般都会考虑算每一位的贡献”。

这里“每一位”指的是二进制拆分后的每一位。

意识到二进制拆分之后,由于xor的性质,可以对于数位状态取方案数的前缀和。求出前缀和之后,“然后前缀xor和之后问题变成要从左区间选出一个前缀和是0,右区间选出一个前缀和是1或者从左区间选出一个前缀和是1,右区间选出一个前缀和是0的方案数。”。

换而言之就是在二进制上面一位一位算过去:当前位被算到答案里面几次。

 

思路挺妙的吧,但是好像对于老练的选手来说是道一眼题?……

 

 1 #include<bits/stdc++.h>
 2 const int MO = 1000000007;
 3 const int maxn = 100035;
 4 
 5 int n,a[maxn];
 6 int l[maxn],r[maxn];
 7 int stk[maxn],cnt;
 8 int s[maxn][2];
 9 int ans;
10 
11 int read()
12 {
13     char ch = getchar();
14     int num = 0, fl = 1;
15     for (; !isdigit(ch); ch = getchar())
16         if (ch==-) fl = -1;
17     for (; isdigit(ch); ch = getchar())
18         num = (num<<1)+(num<<3)+ch-48;
19     return num*fl;
20 }
21 int main()
22 {
23     freopen("magic.in","r",stdin);
24     freopen("magic.out","w",stdout);
25     n = read();
26     for (int i=1; i<=n; i++) a[i] = read();
27     cnt = 0;
28     for (int i=1; i<=n; i++)      //单调栈处理max左边界
29     {
30         while (cnt&&a[stk[cnt]] < a[i]) cnt--;
31         l[i] = stk[cnt];
32         stk[++cnt] = i;
33     }
34     cnt = 0, stk[0] = n+1;
35     for (int i=n; i>=1; i--)      //单调栈处理max右边界
36     {
37         while (cnt&&a[stk[cnt]] <= a[i]) cnt--;
38         r[i] = stk[cnt];
39         stk[++cnt] = i;
40     }
41     s[1][0] = 1;             //处理数位边界条件
42     for (int t=1; t<=MO; t<<=1)    //按位统计贡献
43     {
44         int tot = 0;
45         for (int i=2; i<=n+1; i++)
46         {
47             tot ^= a[i-1];
48             if (tot&t)          //计算方案数前缀和
49                 s[i][1] = s[i-1][1]+1, s[i][0] = s[i-1][0];
50             else s[i][1] = s[i-1][1], s[i][0] = s[i-1][0]+1;
51         }
52         tot = 0;
53         for (int i=1; i<=n; i++)    //按照方案数统计贡献
54             tot = (tot+(1ll*(s[r[i]][0]-s[i][0])*(s[i][1]-s[l[i]][1])+1ll*(s[r[i]][1]-s[i][1])*(s[i][0]-s[l[i]][0]))%MO*a[i])%MO;
55         ans = (ans+1ll*t*tot)%MO;    //因为贡献的是这位的值,所以要乘t
56     }
57     printf("%d
",ans);
58     return 0;
59 }

 

 

 

 

END

以上是关于单调栈 前缀和 异或7.21序列求和的主要内容,如果未能解决你的问题,请参考以下文章

POJ2796 Feel Good(单调栈)

POI2008 题解

Feel Good 单调递增栈+前缀和

LeetCode 907 子数组的最小值之和[单调栈] HERODING的LeetCode之路

单调队列

51nod 2478单调栈前缀和小b接水