BZOJ 4827: [Hnoi2017]礼物(FFT)

xiaoxiao2021-02-28  100

题目传送门

轻轻戳我


Solution

题目欲求 ni=1(xi+cyi)2 的最小值。

先处理 +c 的问题,将式子展开

i=1n(xi+cyi)2=i=1n(xiyi)2+2i=1n(xiyi)c+nc2

由于那些 x,y 什么的都是常量,于是我们得到了一个关于 c 的开口向上的二次函数,现在要求其极小值,学过初三数学的我直接O(1)算对称轴求出 c

然后 c的限制被去掉了。 原问题理所当然地变成了求 ni=1(xiyi)2 的值。

i=1n(xiyi)2=i=1nx2i+i=1ny2i2i=1nxiyi

前面一堆常量,预处理搞出。最后那项把 y 数组翻转过来后,就是个裸的卷积,直接上FFT

但由于可以旋转,所以其实要求的是循环卷积。

有一个普通的类似破环为链的作法:我们发现如果将 x 旋转,相当于将x倍长, xi 变成 xi+j

于是:

i=1n(xi+jyi)2=i=1nx2i+j+i=1ny2i2i=1nxi+jyi

只用求最后那项,直接将 y 反过来,用FFT求卷积即可。

由于 m 挺小,其实也可以先FFT,再直接枚举 c ,计算出答案。

对于旋转的处理,还有一种神奇的方法:

把两个序列画出来,发现如果旋转一定位置的话,那么答案就是两个序列前i部分卷起来加上把剩下部分卷起来的和。

那么只要正着反着分别做一次 FFT 即可。(还是自己画个图容易懂一点。。)


Code

#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <iostream> #include <cmath> #define MAXN 100010 #define oo 0x7fffffff using namespace std; const double PI = acos(-1.0); int n, m, nn, X[MAXN], Y[MAXN], P[MAXN], sumX, sumY, sum, C, ans; struct Complex{ double real, image; Complex() {} Complex(double _real, double _image){real = _real, image = _image;} friend Complex operator + (Complex A, Complex B){return Complex(A.real + B.real, A.image + B.image);} friend Complex operator - (Complex A, Complex B){return Complex(A.real - B.real, A.image - B.image);} friend Complex operator * (Complex A, Complex B){return Complex(A.real * B.real - A.image * B.image, A.real * B.image + A.image * B.real);} }a[MAXN<<2], b[MAXN<<2], c[MAXN<<2]; void Get_P(){ for(int i = 1, t = 1; i < MAXN; i++) P[i] = (t < i) ? (t <<= 1) : t; } void Reverse(Complex *A){ for(int i = 0; i < nn-1; i++){ int j = 0; for(int k = 1, tmp = i; k < nn; k <<= 1, tmp >>= 1) j = ((j << 1) | (tmp & 1)); if(i < j) swap(A[i], A[j]); } } void FFT(Complex *A, int DFT){ Reverse(A); for(int s = 1; (1<<s) <= nn; s++){ int m = 1 << s; Complex wm = Complex(cos(DFT*2*PI/m), sin(DFT*2*PI/m)); for(int k = 0; k < nn; k += m){ Complex w = Complex(1, 0); for(int j = 0; j < (m>>1); j++){ Complex u = A[k+j], t = w * A[k+j+(m>>1)]; A[k+j] = u + t; A[k+j+(m>>1)] = u - t; w = w * wm; } } } if(!(~DFT)) for(int i = 0; i < nn; i++) A[i].real /= nn; } int main(){ freopen("bzoj4827.in", "r", stdin); freopen("bzoj4827.out", "w", stdout); Get_P(); scanf("%d%d", &n, &m); nn = P[n<<1] << 1; for(int i = 0; i < n; i++) scanf("%d", &X[i]), sumX += X[i]; for(int i = 0; i < n; i++) scanf("%d", &Y[i]), sumY += Y[i]; C = (sumY - sumX) / n; int Val1 = n * C * C + 2 * (sumX - sumY) * C; int Val2 = n * (C+1) * (C+1) + 2 * (sumX - sumY) * (C+1); if(Val1 > Val2) C ++; if(C > 0) for(int i = 0; i < n; i++) X[i] += C; else for(int i = 0; i < n; i++) Y[i] += C; for(int i = 0; i < n; i++) sum += X[i] * X[i], a[i] = a[i+n] = Complex(X[i], 0); for(int i = 0; i < n; i++) sum += Y[i] * Y[i], b[i] = Complex(Y[n-i-1], 0); for(int i = n<<1; i < nn; i++) a[i] = Complex(0, 0); for(int i = n; i < nn; i++) b[i] = Complex(0, 0); FFT(a, 1); FFT(b, 1); for(int i = 0; i < nn; i++) c[i] = a[i] * b[i]; FFT(c, -1); ans = oo; for(int i = n-1; i < (n<<1); i++) ans = min(ans, sum - 2 * (int)(c[i].real+.5)); printf("%d\n", ans); return 0; }

转载请注明原文地址: https://www.6miu.com/read-52252.html

最新回复(0)