zoukankan      html  css  js  c++  java
  • 【单调栈 前缀和 异或】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

  • 相关阅读:
    2017.3.17作业
    2017.3.16作业
    2017.3.15作业
    2017.3.14作业
    2017.3.13作业
    2017.3.10作业
    网站流量日志分析(数据 采集之 Flume 采集)
    网站流量日志分析(模块开发——数据仓库设计)
    日常问题及解决
    可爱的生活
  • 原文地址:https://www.cnblogs.com/antiquality/p/9347851.html
Copyright © 2011-2022 走看看