【联想杯2020】Gentle Jena解题报告

题目链接:https://acm.ecnu.edu.cn/contest/270/problem/G/

这题不算很难,但是思路很巧妙。

不要被巨大的数据量和强制在线吓到了,事实上这个数据量提醒我们一定有 O ( n ) O(n) O(n)算法;由于是函数计算,因此也不可能是贪心。那么思路只有在算贡献和递推两方面了。

因为是强制在线,算贡献也只能递推处理。而 O ( n ) O(n) O(n)的递推必定不可能太复杂,就这样,复杂的题面反倒是提示了一种简单的做法。

观察性质,容易发现如果当前出现的数比前面的某个数小,那这个数必定不可能有新的贡献。那么,每次新加入一个数,一个之前的数要有贡献,当且仅当这个数到新加入的数的区间内没有比它小的数。

那么怎么样维护满足这样的条件的数呢?这里用到一种叫单调栈的方法。想象一个栈,自底向上单调递增;每次加入新数时,弹出所有比它大的数。换句话说,栈中每个数无论值还是在数列中的下标都大于它下面的一个数。这样就排除了“当前出现的数比前面的某个数小”的那些数,使得它们不再被考虑;剩下的,则是仍在“这个数到新加入的数的区间”中为最小值的数。

那么贡献应该如何计算呢?前面说到递推处理。首先,如果这个数已经被弹出栈了,那么不需要考虑;而对于栈内的数字,假设它的下标为 x i x_i xi,而它下面的一个数的下标为 x i − 1 x_{i-1} xi1,那么根据单调栈的性质,在 ( x i − 1 , x i ) (x_{i-1},x_i) (xi1,xi)中不存在比当前数更小的数,因此每次新加入一个数时,以 ( x i − 1 , x i ] (x_{i-1},x_i] (xi1,xi]中任意一个数为区间起点,都可以以新数为区间终点构成新的区间,因此它的贡献值会增加 a x i ( x i − x i − 1 ) a_{x_i}(x_i-x_{i-1}) axi(xixi1)

那么就可以递推答案 f i = f i − 1 + s i f_i=f_{i-1}+s_i fi=fi1+si,其中 s i s_i si为新增贡献, s i = s i − 1 − ∑ a x t ( x t − x t − 1 ) + a i ( x m − x m − 1 ) s_i=s_{i-1}-\sum a_{x_t}(x_t-x_{t-1})+a_i(x_m-x_{m-1}) si=si1axt(xtxt1)+ai(xmxm1)。其中 m m m为新数假如后的栈的容量, t t t为所有被弹出的数在栈中的位置。注意最后输出的是所有 f f f的异或和。

代码:

#include
#define MOD 998244353
using namespace std;
 struct newdata
 {
  long long x,label;
 };
 int n,top;
 long long p,x,y,z,b;
 newdata stack[10000001]; 
int main()
{
 scanf("%d%lld%lld%lld%lld%lld",&n,&p,&x,&y,&z,&b);
 long long sum = 0;
 long long ans = 0;
 long long now = 0;
 for (int i = 1;i <= n;i ++)
 {
  while (top > 0 && b < stack[top].x)
  {
   sum = ((sum - stack[top].x * (stack[top].label - stack[top - 1].label)) % MOD + MOD) % MOD;
   top --;
  }
  now += b * (i - stack[top].label) % MOD + sum;
  now %= MOD;
  ans ^= now;
  sum += b * (i - stack[top].label) % MOD;
  sum %= MOD;
  stack[++top].x = b;
  stack[top].label = i;
  b = (x * now + y * b + z) % p;
 }
 printf("%lld",ans);
 return 0;
}

你可能感兴趣的:(递推与动态规划)