T2695 桶哥的问题——吃桶

题目描述

桶哥的桶没有送完,他还有n个桶。他决定把这些桶吃掉。他的每一个桶两个属性:种类ai和美味值bi。若下标为x, y, z(下标从1开始)的三个桶满足:

x<z切x+y=z-2y且ax=az     那么他们构成一个套餐,会产生(x+z)*(bx-bz)的价值

问:一共会产生多少价值

首先来讲暴力打法:

最暴力的显然就是n^3的三层循环枚举了——20分

再来说一下小小的优化:

我们发现每个套餐所产生的价值和y没有任何关系,而且题目中x+y=z-2y的条件等价于z-x=3y,也就是说只要z-x是3的倍数就可以,所以我们可以不一个个枚举,只需要枚举(z-x)%3==0的情况就好了——40分

接着,继续从条件入手:我们发现x和z的桶的种类是一样的,也就是说我们可以开一个vector数组来存储相同种类的桶,这样就可以进一步加快——60分

正解:

看到这个数据范围,基本上就是O(1)或者O(n log n)的速度了

大体还是枚举,但是我们注意到3这个数字,对于x,一共有三种类型,即xΞ0,1,2,(mod 3),也就是说我们不用一个一个判断,可以直接枚举同余的情况

我们可以把(x+z)*(bx-bz)这个式子拆开:xbx-xbz+zbx-zbz,发现当你枚举了x,再枚举z时,x是不变的,bx也是不变的,所以我们可以用前缀和来存储这些东西

如果变成求和:Σx bx-Σx bz+z Σbx-zbzΣ1

再进一步,我们发现对于每一个枚举的x和z,他们的价值都可以用前缀和来表示,那么我们就把双层枚举变为了一层枚举

代码:

#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>

int readInt() {
  int ans = 0, c, f = 1;
  while (!isdigit(c = getchar()))
    if (c == '-') f *= -1;
  do ans = ans * 10 + c - '0';
  while (isdigit(c = getchar()));
  return ans * f;
}

const int mod = 10007;

int b[100005], a[100005];
int S[100005], Sx[100005], Sbx[100005], Sxbx[100005];

int main() {
  int n = readInt(); /* m = */readInt();
  for(int i = 1; i <= n; i++) b[i] = readInt() % mod;//美味值 
  for(int i = 1; i <= n; i++) a[i] = readInt();//种类 
  int ans = 0;
  for (int cc = 1; cc <= 3; ++cc) {
    // { cc, cc+3, cc+6 ... } 分一组
    memset(S, 0, sizeof(S));
    memset(Sx, 0, sizeof(Sx));
    memset(Sbx, 0, sizeof(Sbx));
    memset(Sxbx, 0, sizeof(Sxbx));
    for(int i = cc; i <= n; i += 3) {
      ans = (ans + i % mod * Sbx[a[i]] % mod) % mod;
      ans = (ans - b[i] * Sx[a[i]] % mod) % mod;
      ans = (ans + Sxbx[a[i]]) % mod;
      ans = (ans - S[a[i]] * b[i] % mod * (i % mod) % mod) % mod;
      //Σx bx - Σx bz + z Σbx - zbz Σ1
      Sbx[a[i]] = (Sbx[a[i]] + b[i]) % mod;//Σbx
      Sx[a[i]] = (Sx[a[i]] + i) % mod;//Σx
      Sxbx[a[i]] = (Sxbx[a[i]] + i % mod * b[i] % mod) % mod;//Σbx
      S[a[i]] = (S[a[i]] + 1) % mod;//Σ1
    }
  }
  printf("%d", (ans + mod) % mod);
  return 0;
}
原文地址:https://www.cnblogs.com/lcezych/p/10939826.html