题目描述
桶哥的桶没有送完,他还有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; }