【单调栈 前缀和 异或】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\n",ans); 58 return 0; 59 }
END